Skip to content

feat(ios) Accessibility Deferral Element and additions#598

Open
RoyalPineapple wants to merge 2 commits intomainfrom
alex/labelDeferral
Open

feat(ios) Accessibility Deferral Element and additions#598
RoyalPineapple wants to merge 2 commits intomainfrom
alex/labelDeferral

Conversation

@RoyalPineapple
Copy link
Collaborator

@RoyalPineapple RoyalPineapple commented Nov 12, 2025

Summary

This PR enhances the accessibility deferral system with comprehensive improvements to frame handling and element composition.

Key Changes

  • ReceiverContainer Element: Introduces AccessibilityDeferral.ReceiverContainer allowing arbitrary elements to receive deferred accessibility content without needing to conform to the AccessibilityDeferral.Receiver protocol directly
  • FrameProvider: Adds centralized accessibility frame handling with support for both CGRect-based and UIView-based frame providers, including automatic corner radius support
  • Accessibility Merging: Implements merge() method on CompositeRepresentation to combine accessibility values from multiple sources
  • Protocol Updates: Updates Receiver protocol with updateDeferredAccessibility(frameProvider:) callback for frame-aware receivers
  • CombinableView changes: Makes CombinableView extensible (changed from final class) and adds mergeValues property for more flexible accessibility composition

Technical Details

The FrameProvider struct provides a unified API for accessibility frame handling that enables UIView-based frame passthrough without taking a strong reference to the backing view.

The ReceiverContainer wraps any element and handles deferred accessibility application automatically, simplifying the integration of accessibility deferral in complex element hierarchies.

@RoyalPineapple RoyalPineapple changed the base branch from main to alex/migrateA11y November 12, 2025 12:34
Base automatically changed from alex/migrateA11y to main November 25, 2025 12:14
@RoyalPineapple RoyalPineapple force-pushed the alex/labelDeferral branch 3 times, most recently from 80b442d to 025d5b2 Compare December 16, 2025 19:06
@RoyalPineapple RoyalPineapple changed the title [WIP DNR] Alex/label deferral [WIP DNR] Accessibility Deferral element Jan 28, 2026
@RoyalPineapple RoyalPineapple force-pushed the alex/labelDeferral branch 2 times, most recently from a4cc843 to ee29332 Compare January 29, 2026 11:17

func replaceContent(_ content: [AccessibilityDeferral.Content]?) {
deferredAccessibilityContent = content
internal func replaceContent(_ content: [AccessibilityDeferral.Content]?) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moving this internal as it doesn't need to be the responsibility of the consumer and this removes some foot guns

@RoyalPineapple RoyalPineapple force-pushed the alex/labelDeferral branch 2 times, most recently from 72fcecd to 9ce750c Compare January 29, 2026 15:17
… element

This introduces comprehensive accessibility deferral improvements:
- Add ReceiverContainer element to expose deferred accessibility content
- Add FrameProvider to centralize accessibility frame handling
- Add merge() method to combine accessibility representations
- Apply container frames and corner radius to accessibility paths
- Refactor Receiver protocol with updateDeferredAccessibility callback
- Make CombinableView extensible and add mergeValues support
- Update array extensions to package visibility

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

/// Custom content that may be supplied in addition to the deferred content
var customContent: [Accessibility.CustomContent]? { get set }
var customContent: [BlueprintUI.Accessibility.CustomContent]? { get set }
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure we need this

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I think kill it

@RoyalPineapple RoyalPineapple changed the title [WIP DNR] Accessibility Deferral element feat(ios) Accessibility Deferral Element and additions Jan 29, 2026
@RoyalPineapple RoyalPineapple marked this pull request as ready for review March 2, 2026 12:33
@RoyalPineapple RoyalPineapple requested a review from a team as a code owner March 2, 2026 12:33
Copy link
Contributor

@johnnewman-square johnnewman-square left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These changes look good. I'm going to give the Market integration a pass and will circle back.

I think it would be great if we could add new unit tests for these deferral updates, if possible.

Comment on lines +346 to +349
mergeValues = deferred.dropFirst()
.reduce(into: first) { result, value in
result.merge(with: value)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will the result of result.merge(with: value) be discarded here? I wonder if we could tweak this reduce pattern just a bit and add a unit test that captures this particular flow.

Comment on lines +148 to +152

/// Creates a `ReceiverContainer` element to expose the deferred accessibility.
public func deferredAccessibilityReceiver(identifiers: [AnyHashable]) -> AccessibilityDeferral.ReceiverContainer {
AccessibilityDeferral.ReceiverContainer(wrapping: { self })
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we update this function to use or remove the identifiers parameter?

result.merge(with: value)
}
}
needsAccessibilityUpdate = true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are two calls to needsAccessibilityUpdate = true in this function. Could we remove one?

public var sourceIdentifier: AnyHashable

/// : A stable identifier used to identify a given update pass through he view hierarchy. Content with matching updateIdentifiers should be combined.
/// A stable identifier used to identify a given update pass through he view hierarchy. Content with matching updateIdentifiers should be combined.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// A stable identifier used to identify a given update pass through he view hierarchy. Content with matching updateIdentifiers should be combined.
/// A stable identifier used to identify a given update pass through the view hierarchy. Content with matching updateIdentifiers should be combined.


/// Custom content that may be supplied in addition to the deferred content
var customContent: [Accessibility.CustomContent]? { get set }
var customContent: [BlueprintUI.Accessibility.CustomContent]? { get set }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I think kill it

/// - cornerRadius: The radius for rounded corners
public static func frame(_ rect: CGRect, cornerRadius: CGFloat = accessibilityCornerRadius) -> Self {
.init {
UIBezierPath(roundedRect: rect, cornerRadius: max(0, cornerRadius + accessibilityPathInset))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this really be negative?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants