Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
369cec6
feat(accessibility): implement focus matching and platform helpers
Kyle-Ye Sep 25, 2026
fbc335e
feat: add OpenSwiftUI+NSObject
Kyle-Ye Sep 27, 2026
51a3a60
feat: update AccessibilityNodeList
Kyle-Ye Sep 27, 2026
214ef52
feat: update AccessibilityNode
Kyle-Ye Sep 27, 2026
95bc9a5
feat: update AccessibilityPrivate
Kyle-Ye Sep 27, 2026
99536f1
feat: add AccessibilityCombinable
Kyle-Ye Sep 27, 2026
75eca4e
feat: add AccessibilityIdentifierStorage
Kyle-Ye Sep 27, 2026
be08bed
feat: add AccessibilityLabelStorage
Kyle-Ye Sep 27, 2026
09db3ad
chore: update AccessibilityAnnouncementPriority
Kyle-Ye Sep 27, 2026
dd8ca7a
feat(accessibility): implement child behavior kind
Kyle-Ye Sep 27, 2026
d37c9f0
feat(accessibility): add accessibility value types
Kyle-Ye Sep 27, 2026
4a55651
feat(accessibility): update AccessibilityProperties
Kyle-Ye Sep 27, 2026
0aa9323
feat(accessibility): add accessibility property keys
Kyle-Ye Sep 27, 2026
e2ec443
feat(accessibility): implement nullable option sets
Kyle-Ye Sep 27, 2026
962be9a
feat(accessibility): implement visibility storage
Kyle-Ye Sep 27, 2026
7ce0510
feat(accessibility): add AccessibilityTraits
Kyle-Ye Sep 27, 2026
18f0cbd
chore: remove comments
Kyle-Ye Sep 27, 2026
cad37ee
feat(accessibility): implement activation points
Kyle-Ye Sep 27, 2026
2d702e5
feat: move LinkDestination to OpenSwiftUICore
Kyle-Ye Sep 27, 2026
2962be6
feat(accessibility): add AXAutomationType SPI
Kyle-Ye Sep 27, 2026
ce0356d
feat(accessibility): add chart descriptor support
Kyle-Ye Sep 27, 2026
401d002
feat(accessibility): add custom attributes
Kyle-Ye Sep 27, 2026
963c756
feat(accessibility): add data series configuration
Kyle-Ye Sep 27, 2026
f9b72ad
feat(accessibility): add custom content support
Kyle-Ye Sep 27, 2026
3e4b862
docs(accessibility): document public APIs
Kyle-Ye Sep 27, 2026
022d754
feat(accessibility): implement core helpers
Kyle-Ye Sep 27, 2026
3ce41ee
fix(accessibility): import platform modules for node base
Kyle-Ye Sep 27, 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//
// OpenSwiftUIAccessibilityPrivate.h
// COpenSwiftUI
//
// Audited for 6.5.4
// Status: Complete

#ifndef OpenSwiftUIAccessibilityPrivate_h
#define OpenSwiftUIAccessibilityPrivate_h

#include "OpenSwiftUIBase.h"

#if OPENSWIFTUI_TARGET_OS_DARWIN

#import <Foundation/Foundation.h>
#if OPENSWIFTUI_TARGET_OS_OSX
#import <AppKit/AppKit.h>
#endif

OPENSWIFTUI_ASSUME_NONNULL_BEGIN

#if OPENSWIFTUI_TARGET_OS_OSX
typedef OPENSWIFTUI_ENUM(NSInteger, AXAttributeLockSource) {
AXAttributeLockSourceNone = 0,
};
#endif

@interface NSObject (OpenSwiftUIAccessibilityPrivate)
+ (BOOL)_isFromOpenSwiftUI;
- (void)_performSelector:(SEL)selector withObject:(nullable id)object;

@property (nonatomic, nullable, copy) BOOL (^accessibilityOpenSwiftUIDefaultActionStoredBlock)(void);
@property (nonatomic, nullable, strong) id accessibilityOpenSwiftUIStoredLinkRotor;
@property (nonatomic, nullable, strong) id accessibilityNodeForPlatformElement;

#if OPENSWIFTUI_TARGET_OS_OSX
@property (nonatomic) BOOL _accessibilityAllowsAutomationElements;
- (AXAttributeLockSource)accessibilityLockSourceForAttribute:(NSAccessibilityAttributeName)attribute OPENSWIFTUI_SWIFT_NAME(accessibilityLockSource(for:));
- (void)setAccessibilityLockSource:(AXAttributeLockSource)source forAttribute:(NSAccessibilityAttributeName)attribute OPENSWIFTUI_SWIFT_NAME(setAccessibilityLockSource(_:for:));
#endif
@end

OPENSWIFTUI_ASSUME_NONNULL_END

#endif /* OPENSWIFTUI_TARGET_OS_DARWIN */

#endif /* OpenSwiftUIAccessibilityPrivate_h */
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//
// OpenSwiftUIAccessibilityPrivate.m
// COpenSwiftUI
//
// Audited for 6.5.4
// Status: Complete

#import "OpenSwiftUIAccessibilityPrivate.h"
#import "../Foundation/OpenSwiftUI+NSObject.h"

#if OPENSWIFTUI_TARGET_OS_IOS || OPENSWIFTUI_TARGET_OS_VISION || OPENSWIFTUI_TARGET_OS_OSX

#import <objc/runtime.h>

@implementation NSObject (OpenSwiftUIAccessibilityPrivate)

Check warning on line 15 in Sources/COpenSwiftUI/Overlay/Accessibility/OpenSwiftUIAccessibilityPrivate.m

View workflow job for this annotation

GitHub Actions / Execute tests on iOS (26.6, 2024, 18.5)

property 'accessibilityNodeForPlatformElement' requires method 'setAccessibilityNodeForPlatformElement:' to be defined - use @dynamic or provide a method implementation in this category [-Wobjc-property-implementation]

Check warning on line 15 in Sources/COpenSwiftUI/Overlay/Accessibility/OpenSwiftUIAccessibilityPrivate.m

View workflow job for this annotation

GitHub Actions / Execute tests on iOS (26.6, 2024, 18.5)

property 'accessibilityNodeForPlatformElement' requires method 'accessibilityNodeForPlatformElement' to be defined - use @dynamic or provide a method implementation in this category [-Wobjc-property-implementation]

Check warning on line 15 in Sources/COpenSwiftUI/Overlay/Accessibility/OpenSwiftUIAccessibilityPrivate.m

View workflow job for this annotation

GitHub Actions / Execute tests on iOS (26.6, 2024, 18.5)

method definition for '_isFromOpenSwiftUI' not found [-Wincomplete-implementation]

Check warning on line 15 in Sources/COpenSwiftUI/Overlay/Accessibility/OpenSwiftUIAccessibilityPrivate.m

View workflow job for this annotation

GitHub Actions / Execute tests on iOS (26.6, 2024, 18.5)

method definition for '_performSelector:withObject:' not found [-Wincomplete-implementation]
- (BOOL (^)(void))accessibilityOpenSwiftUIDefaultActionStoredBlock {
return objc_getAssociatedObject(self, @selector(accessibilityOpenSwiftUIDefaultActionStoredBlock));
}

- (void)setAccessibilityOpenSwiftUIDefaultActionStoredBlock:(BOOL (^)(void))block {
objc_setAssociatedObject(self, @selector(accessibilityOpenSwiftUIDefaultActionStoredBlock), block, OBJC_ASSOCIATION_COPY);
}

- (id)accessibilityOpenSwiftUIStoredLinkRotor {
return objc_getAssociatedObject(self, @selector(accessibilityOpenSwiftUIStoredLinkRotor));
}

- (void)setAccessibilityOpenSwiftUIStoredLinkRotor:(id)rotor {
objc_setAssociatedObject(self, @selector(accessibilityOpenSwiftUIStoredLinkRotor), rotor, OBJC_ASSOCIATION_RETAIN);
}

#if OPENSWIFTUI_TARGET_OS_OSX
- (AXAttributeLockSource)accessibilityLockSourceForAttribute:(NSAccessibilityAttributeName)attribute {
NSDictionary<NSAccessibilityAttributeName, NSNumber *> *sources = objc_getAssociatedObject(self, @selector(accessibilityLockSourceForAttribute:));
return [sources[attribute] integerValue];
}

- (void)setAccessibilityLockSource:(AXAttributeLockSource)source forAttribute:(NSAccessibilityAttributeName)attribute {
NSMutableDictionary<NSAccessibilityAttributeName, NSNumber *> *sources = objc_getAssociatedObject(self, @selector(accessibilityLockSourceForAttribute:));
if (sources == nil) {
sources = [[NSMutableDictionary alloc] init];
objc_setAssociatedObject(self, @selector(accessibilityLockSourceForAttribute:), sources, OBJC_ASSOCIATION_RETAIN);
}
sources[attribute] = @(source);
}

- (BOOL)_accessibilityAllowsAutomationElements {
NSNumber *value = objc_getAssociatedObject(self, @selector(_accessibilityAllowsAutomationElements));
return value.boolValue;
}

- (void)set_accessibilityAllowsAutomationElements:(BOOL)value {
objc_setAssociatedObject(self, @selector(_accessibilityAllowsAutomationElements), @(value), OBJC_ASSOCIATION_RETAIN);
}
#endif
@end

#endif /* OPENSWIFTUI_TARGET_OS_IOS || OPENSWIFTUI_TARGET_OS_VISION || OPENSWIFTUI_TARGET_OS_OSX */
28 changes: 28 additions & 0 deletions Sources/COpenSwiftUI/Overlay/Foundation/OpenSwiftUI+NSObject.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//
// OpenSwiftUI+NSObject.h
// COpenSwiftUI
//
// Audited for 6.5.4
// Status: Complete

#ifndef OpenSwiftUI_NSObject_h
#define OpenSwiftUI_NSObject_h

#include "OpenSwiftUIBase.h"

#if OPENSWIFTUI_TARGET_OS_DARWIN

#import <Foundation/Foundation.h>

OPENSWIFTUI_ASSUME_NONNULL_BEGIN

@interface NSObject (OpenSwiftUI)
+ (BOOL)_isFromOpenSwiftUI;
- (void)_performSelector:(SEL)selector withObject:(nullable id)object;
@end

OPENSWIFTUI_ASSUME_NONNULL_END

#endif /* OPENSWIFTUI_TARGET_OS_DARWIN */

#endif /* OpenSwiftUI_NSObject_h */
25 changes: 25 additions & 0 deletions Sources/COpenSwiftUI/Overlay/Foundation/OpenSwiftUI+NSObject.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//
// OpenSwiftUI+NSObject.m
// COpenSwiftUI
//
// Audited for 6.5.4
// Status: Complete

#import "OpenSwiftUI+NSObject.h"

#if OPENSWIFTUI_TARGET_OS_DARWIN

@implementation NSObject (OpenSwiftUI)
+ (BOOL)_isFromOpenSwiftUI {
return NO;
}

- (void)_performSelector:(SEL)selector withObject:(id)object {
if (object != nil) {
void (*implementation)(id, SEL) = (void (*)(id, SEL))[object methodForSelector:selector];
implementation(object, selector);
}
}
@end

#endif /* OPENSWIFTUI_TARGET_OS_DARWIN */
16 changes: 16 additions & 0 deletions Sources/COpenSwiftUI/Shims/AppKit/AppKit_Private.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,22 @@

OPENSWIFTUI_ASSUME_NONNULL_BEGIN

OPENSWIFTUI_EXTERN_C_BEGIN

OPENSWIFTUI_EXPORT
Class _Nullable AXNSTableViewCellMockElementClass(void);

OPENSWIFTUI_EXPORT
void NSAccessibilityBeginInternalAccessors(void);

OPENSWIFTUI_EXPORT
void NSAccessibilityEndInternalAccessors(void);

OPENSWIFTUI_EXPORT
id _Nullable NSAccessibilityEntryPointValueForAttribute(id element, NSAccessibilityAttributeName attribute);

OPENSWIFTUI_EXTERN_C_END

@interface NSApplication (OpenSwiftUI_SPI)

- (BOOL)_shouldLoadMainNibNamed:(nullable NSString *)name;
Expand Down
9 changes: 9 additions & 0 deletions Sources/COpenSwiftUI/Shims/AppKit/AppKit_Private.m
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,15 @@

#import <objc/runtime.h>

Class _Nullable AXNSTableViewCellMockElementClass(void) {
static Class tableViewCellMockElementClass;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
tableViewCellMockElementClass = NSClassFromString(@"NSTableViewCellMockElement");
});
return tableViewCellMockElementClass;
}

@implementation NSApplication (OpenSwiftUI_SPI)

- (void)markAppLaunchComplete_openswiftui_safe_wrapper {
Expand Down
48 changes: 48 additions & 0 deletions Sources/OpenSwiftUI/Accessibility/AccessibilityAdditions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//
// AccessibilityAdditions.swift
// OpenSwiftUI
//
// Audited for 6.5.4
// Status: Complete
// ID: F0090706A080B6CC8EC7D33D6ADC922D (SwiftUI)

import Foundation
import OpenSwiftUICore
#if canImport(ObjectiveC)
import ObjectiveC
#endif
import COpenSwiftUI

// MARK: - NSObject + AccessibilityNode

#if canImport(ObjectiveC)
@objc(OpenSwiftUIAccessibilityPrivate)
#endif
extension NSObject {
var accessibilityNodeForPlatformElement: AccessibilityNode? {
get {
#if canImport(ObjectiveC)
objc_getAssociatedObject(
self,
&accessibilityNodeForPlatformElementKey
) as? AccessibilityNode
#else
_openSwiftUIPlatformUnimplementedFailure()
#endif
}
set {
#if canImport(ObjectiveC)
objc_setAssociatedObject(
self,
&accessibilityNodeForPlatformElementKey,
newValue,
.OBJC_ASSOCIATION_ASSIGN
)
#else
_openSwiftUIPlatformUnimplementedFailure()
#endif
}
}
}

private var accessibilityNodeForPlatformElementKey: UInt8 = 0
122 changes: 122 additions & 0 deletions Sources/OpenSwiftUI/Accessibility/AccessibilityFocus.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
//
// AccessibilityFocus.swift
// OpenSwiftUI
//
// Audited for 6.5.4
// Status: Complete
// ID: D40E4D2B9502265C452AA4AA6D326657 (SwiftUI)

import Foundation
import OpenAttributeGraphShims
@_spi(ForOpenSwiftUIOnly)
import OpenSwiftUICore
#if os(macOS)
import AppKit
import COpenSwiftUI
#endif

// MARK: - AccessibilityFocus

struct AccessibilityFocus {
var byTechnology: [AccessibilityTechnology: Target] = [:]

struct Target {
weak var platformAccessibilityElement: PlatformAccessibilityElement?

var ancestorAccessibilityNode: AccessibilityNode? {
if let node = platformAccessibilityElement as? AccessibilityNode {
return node
}
if let node = platformAccessibilityElement?.accessibilityNodeForPlatformElement {
return node
}
guard let platformAccessibilityElement else {
return nil
}
if let node = platformAccessibilityElement.accessibilityNodeForPlatformElement {
return node
}
var node: AccessibilityNode?
platformAccessibilityElement.traverseAncestors { element in
if let ancestor = element as? AccessibilityNode {
node = ancestor
return false
}
if let ancestor = element.accessibilityNodeForPlatformElement {
node = ancestor
return false
}
return true
}
return node
}

func match(focusStoreNode: AccessibilityNode) -> Match? {
if let node = (platformAccessibilityElement as? AccessibilityNode)
?? platformAccessibilityElement?.accessibilityNodeForPlatformElement,
node == focusStoreNode {
return .directlyFocused
}
if let element = focusStoreNode.platformElement,
let target = platformAccessibilityElement,
element === target {
return .directlyFocused
}
if let ancestor = ancestorAccessibilityNode,
ancestor == focusStoreNode {
return .platformChildFocused
}
if let ancestor = ancestorAccessibilityNode {
var isContainerChildFocused = false
ancestor.traverseAncestors { element in
if element === (focusStoreNode.platformElement ?? focusStoreNode) {
isContainerChildFocused = true
return false
}
return true
}
if isContainerChildFocused {
return .containerChildFocused
}
}
#if os(macOS)
if let element = platformAccessibilityElement,
let tableCellClass = AXNSTableViewCellMockElementClass(),
element.isKind(of: tableCellClass),
let children = element.valueForAttribute(.children, asType: [PlatformAccessibilityElement].self),
children.count == 1,
children[0] === (focusStoreNode.platformElement ?? focusStoreNode) {
return .implicitlyFocused
}
#endif
return nil
}
}

enum Match: CaseIterable, Hashable {
case directlyFocused
case implicitlyFocused
case platformChildFocused
case containerChildFocused
}
}

// MARK: - AccessibilityFocusInputKey

private struct AccessibilityFocusInputKey: ViewInput {
static var defaultValue: OptionalAttribute<AccessibilityFocus> { .init() }
}

extension _ViewInputs {
var accessibilityFocus: Attribute<AccessibilityFocus>? {
get { base.accessibilityFocus }
set { base.accessibilityFocus = newValue }
}
}

extension _GraphInputs {
var accessibilityFocus: Attribute<AccessibilityFocus>? {
get { self[AccessibilityFocusInputKey.self].attribute }
set { self[AccessibilityFocusInputKey.self] = .init(newValue) }
}
}
Loading
Loading