From bb6627b117cd7a36b709702e6b56a6f69cca6610 Mon Sep 17 00:00:00 2001 From: Mattes Mohr Date: Fri, 24 Apr 2026 09:05:04 +0200 Subject: [PATCH 1/3] Introduce the abbreviated attribute --- .../Attributes/BasicAttributes.swift | 42 +++++++++++++++++++ Tests/HTMLKitTests/AttributesTests.swift | 28 ++++++++++++- 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/Sources/HTMLKit/Abstraction/Attributes/BasicAttributes.swift b/Sources/HTMLKit/Abstraction/Attributes/BasicAttributes.swift index d28141b6..ff9feb07 100644 --- a/Sources/HTMLKit/Abstraction/Attributes/BasicAttributes.swift +++ b/Sources/HTMLKit/Abstraction/Attributes/BasicAttributes.swift @@ -4159,3 +4159,45 @@ extension PopoverTargetAttribute where Self: EmptyNode { return self.mutate(key: "popovertargetaction", value: value) } } + +/// A type that provides the `abbreviated` modifier. +@_documentation(visibility: internal) +public protocol AbbreviatedAttribute: Attribute { + + /// Announce a brief alternate label. + /// + /// - Parameter value: The alternate label to describe the content. + /// + /// - Returns: The element + func abbreviated(_ value: String) -> Self + + /// Announce a brief localized alternate label. + /// + /// - Parameters: + /// - localizedKey: The string key to be translated. + /// - tableName: The translation table to look in. + /// + /// - Returns: The element + func abbreviated(_ localizedKey: LocalizedStringKey, tableName: String?) -> Self + + /// Announce a brief alternate label without localization. + /// + /// - Parameter value: The alternate label to describe the content. + /// + /// - Returns: The element + func abbreviated(verbatim value: String) -> Self +} + +extension AbbreviatedAttribute where Self: ContentNode { + + internal func mutate(abbr value: AttributeData) -> Self { + return self.mutate(key: "abbr", value: value) + } +} + +extension AbbreviatedAttribute where Self: EmptyNode { + + internal func mutate(abbr value: AttributeData) -> Self { + return self.mutate(key: "abbr", value: value) + } +} diff --git a/Tests/HTMLKitTests/AttributesTests.swift b/Tests/HTMLKitTests/AttributesTests.swift index 6e2d0ec5..09389a03 100644 --- a/Tests/HTMLKitTests/AttributesTests.swift +++ b/Tests/HTMLKitTests/AttributesTests.swift @@ -15,7 +15,7 @@ final class AttributesTests: XCTestCase { @ContentBuilder var body: Content } - typealias AllAttributes = AccessKeyAttribute & AcceptAttribute & ActionAttribute & AlternateAttribute & AsynchronouslyAttribute & AutocapitalizeAttribute & AutocompleteAttribute & AutofocusAttribute & AutoplayAttribute & CharsetAttribute & CheckedAttribute & CiteAttribute & ClassAttribute & ColumnsAttribute & ColumnSpanAttribute & ContentAttribute & EditAttribute & ControlsAttribute & DataAttribute & DateTimeAttribute & DefaultAttribute & DeferAttribute & DirectionAttribute & DisabledAttribute & DownloadAttribute & DragAttribute & EncodingAttribute & EnterKeyAttribute & ForAttribute & FormAttribute & FormActionAttribute & EquivalentAttribute & HeadersAttribute & HeightAttribute & HiddenAttribute & HighAttribute & ReferenceAttribute & ReferenceLanguageAttribute & IdentifierAttribute & IsMapAttribute & InputModeAttribute & IsAttribute & ItemAttribute & ItemPropertyAttribute & KindAttribute & LabelAttribute & LanguageAttribute & ListAttribute & LoopAttribute & LowAttribute & MaximumValueAttribute & MaximumLengthAttribute & MediaAttribute & MethodAttribute & MinimumValueAttribute & MinimumLengthAttribute & MultipleAttribute & MutedAttribute & NameAttribute & NonceAttribute & NoValidateAttribute & OpenAttribute & OptimumAttribute & PatternAttribute & PartAttribute & PingAttribute & PlaceholderAttribute & PosterAttribute & PreloadAttribute & ReadOnlyAttribute & ReferrerPolicyAttribute & RelationshipAttribute & RequiredAttribute & ReversedAttribute & RoleAttribute & RowsAttribute & RowSpanAttribute & SandboxAttribute & ScopeAttribute & ShapeAttribute & SizeAttribute & SizesAttribute & SlotAttribute & SpanAttribute & SpellCheckAttribute & SourceAttribute & StartAttribute & StepAttribute & StyleAttribute & TabulatorAttribute & TargetAttribute & TitleAttribute & TranslateAttribute & TypeAttribute & ValueAttribute & WidthAttribute & WrapAttribute & PropertyAttribute & SelectedAttribute & WindowEventAttribute & FocusEventAttribute & PointerEventAttribute & MouseEventAttribute & WheelEventAttribute & InputEventAttribute & KeyboardEventAttribute & DragEventAttribute & ClipboardEventAttribute & SelectionEventAttribute & MediaEventAttribute & FormEventAttribute & DetailEventAttribute & AtomicAccessibilityAttribute & BusyAccessibilityAttribute & ControlsAccessibilityAttribute & CurrentAccessibilityAttribute & DescriptionsAccessibilityAttribute & DetailAccessibilityAttribute & DisabledAccessibilityAttribute & FlowAccessibilityAttribute & PopupAccessibilityAttribute & HiddenAccessibilityAttribute & InvalidAccessibilityAttribute & ShortcutsAccessibilityAttribute & LabelAccessibilityAttribute & LabelsAccessibilityAttribute & LiveAccessibilityAttribute & OwnsAccessibilityAttribute & MultilineAccessibilityAttribute & RowIndexAccessibilityAttribute & RelevantAccessibilityAttribute & RoleDescriptionAccessibilityAttribute & SortAccessibilityAttribute & OrientationAccessibilityAttribute & RequiredAccessibilityAttribute & ReadOnlyAccessibilityAttribute & ModalAccessibilityAttribute & LevelAccessibilityAttribute & HintAccessibilityAttribute & PositionAccessibilityAttribute & MultiselectAccessibilityAttribute & RowCountAccessibilityAttribute & ColumnIndexAccessibilityAttribute & ColumnCountAccessibilityAttribute & RowSpanAccessibilityAttribute & ColumnSpanAccessibilityAttribute & MaximumValueAccessibilityAttribute & MinimumValueAccessibilityAttribute & ValueAccessibilityAttribute & PressedAccessibilityAttribute & SelectedAccessibilityAttribute & CheckedAccessibilityAttribute & ExpandedAccessibilityAttribute & FocusedAccessibilityAttribute & CompletionAccessibilityAttribute & DrawAttribute & FillAttribute & StrokeAttribute & RadiusAttribute & PositionPointAttribute & RadiusPointAttribute & CenterPointAttribute & ViewBoxAttribute & NamespaceAttribute & PointsAttribute & ShadowRootModeAttribute & InertAttribute & FetchPriorityAttribute & LoadingAttribute & SourceSetAttribute & DecodingAttribute & BlockingAttribute & PopoverAttribute & PopoverTargetAttribute & UseMapAttribute & PlaysInlineAttribute & IntegrityAttribute & AsAttribute & CrossOriginAttribute & SourceLanguageAttribute & SourceDocumentAttribute + typealias AllAttributes = AccessKeyAttribute & AcceptAttribute & ActionAttribute & AlternateAttribute & AsynchronouslyAttribute & AutocapitalizeAttribute & AutocompleteAttribute & AutofocusAttribute & AutoplayAttribute & CharsetAttribute & CheckedAttribute & CiteAttribute & ClassAttribute & ColumnsAttribute & ColumnSpanAttribute & ContentAttribute & EditAttribute & ControlsAttribute & DataAttribute & DateTimeAttribute & DefaultAttribute & DeferAttribute & DirectionAttribute & DisabledAttribute & DownloadAttribute & DragAttribute & EncodingAttribute & EnterKeyAttribute & ForAttribute & FormAttribute & FormActionAttribute & EquivalentAttribute & HeadersAttribute & HeightAttribute & HiddenAttribute & HighAttribute & ReferenceAttribute & ReferenceLanguageAttribute & IdentifierAttribute & IsMapAttribute & InputModeAttribute & IsAttribute & ItemAttribute & ItemPropertyAttribute & KindAttribute & LabelAttribute & LanguageAttribute & ListAttribute & LoopAttribute & LowAttribute & MaximumValueAttribute & MaximumLengthAttribute & MediaAttribute & MethodAttribute & MinimumValueAttribute & MinimumLengthAttribute & MultipleAttribute & MutedAttribute & NameAttribute & NonceAttribute & NoValidateAttribute & OpenAttribute & OptimumAttribute & PatternAttribute & PartAttribute & PingAttribute & PlaceholderAttribute & PosterAttribute & PreloadAttribute & ReadOnlyAttribute & ReferrerPolicyAttribute & RelationshipAttribute & RequiredAttribute & ReversedAttribute & RoleAttribute & RowsAttribute & RowSpanAttribute & SandboxAttribute & ScopeAttribute & ShapeAttribute & SizeAttribute & SizesAttribute & SlotAttribute & SpanAttribute & SpellCheckAttribute & SourceAttribute & StartAttribute & StepAttribute & StyleAttribute & TabulatorAttribute & TargetAttribute & TitleAttribute & TranslateAttribute & TypeAttribute & ValueAttribute & WidthAttribute & WrapAttribute & PropertyAttribute & SelectedAttribute & WindowEventAttribute & FocusEventAttribute & PointerEventAttribute & MouseEventAttribute & WheelEventAttribute & InputEventAttribute & KeyboardEventAttribute & DragEventAttribute & ClipboardEventAttribute & SelectionEventAttribute & MediaEventAttribute & FormEventAttribute & DetailEventAttribute & AtomicAccessibilityAttribute & BusyAccessibilityAttribute & ControlsAccessibilityAttribute & CurrentAccessibilityAttribute & DescriptionsAccessibilityAttribute & DetailAccessibilityAttribute & DisabledAccessibilityAttribute & FlowAccessibilityAttribute & PopupAccessibilityAttribute & HiddenAccessibilityAttribute & InvalidAccessibilityAttribute & ShortcutsAccessibilityAttribute & LabelAccessibilityAttribute & LabelsAccessibilityAttribute & LiveAccessibilityAttribute & OwnsAccessibilityAttribute & MultilineAccessibilityAttribute & RowIndexAccessibilityAttribute & RelevantAccessibilityAttribute & RoleDescriptionAccessibilityAttribute & SortAccessibilityAttribute & OrientationAccessibilityAttribute & RequiredAccessibilityAttribute & ReadOnlyAccessibilityAttribute & ModalAccessibilityAttribute & LevelAccessibilityAttribute & HintAccessibilityAttribute & PositionAccessibilityAttribute & MultiselectAccessibilityAttribute & RowCountAccessibilityAttribute & ColumnIndexAccessibilityAttribute & ColumnCountAccessibilityAttribute & RowSpanAccessibilityAttribute & ColumnSpanAccessibilityAttribute & MaximumValueAccessibilityAttribute & MinimumValueAccessibilityAttribute & ValueAccessibilityAttribute & PressedAccessibilityAttribute & SelectedAccessibilityAttribute & CheckedAccessibilityAttribute & ExpandedAccessibilityAttribute & FocusedAccessibilityAttribute & CompletionAccessibilityAttribute & DrawAttribute & FillAttribute & StrokeAttribute & RadiusAttribute & PositionPointAttribute & RadiusPointAttribute & CenterPointAttribute & ViewBoxAttribute & NamespaceAttribute & PointsAttribute & ShadowRootModeAttribute & InertAttribute & FetchPriorityAttribute & LoadingAttribute & SourceSetAttribute & DecodingAttribute & BlockingAttribute & PopoverAttribute & PopoverTargetAttribute & UseMapAttribute & PlaysInlineAttribute & IntegrityAttribute & AsAttribute & CrossOriginAttribute & SourceLanguageAttribute & SourceDocumentAttribute & AbbreviatedAttribute struct Tag: ContentNode, GlobalElement, AllAttributes { @@ -1172,6 +1172,19 @@ final class AttributesTests: XCTestCase { return self } + + @_disfavoredOverload + func abbreviated(_ value: String) -> Tag { + return self.mutate(abbr: .init(value, context: .tainted(.html))) + } + + func abbreviated(_ localizedKey: HTMLKit.LocalizedStringKey, tableName: String? = nil) -> Tag { + return self.mutate(abbr: .init(LocalizedString(key: localizedKey, table: tableName), context: .tainted(.html))) + } + + func abbreviated(verbatim value: String) -> Tag { + return self.mutate(abbr: .init(value, context: .tainted(.html))) + } } var renderer = Renderer() @@ -3811,4 +3824,17 @@ final class AttributesTests: XCTestCase { """ ) } + + func testAbbreviatedAttribute() throws { + + let view = TestView { + Tag {}.abbreviated("HTML") + } + + XCTAssertEqual(try renderer.render(view: view), + """ + + """ + ) + } } From 21608d39903335dcb3d6c1c0bfe0d093610ad991 Mon Sep 17 00:00:00 2001 From: Mattes Mohr Date: Fri, 24 Apr 2026 09:07:42 +0200 Subject: [PATCH 2/3] Add conformance to the element --- .../Abstraction/Elements/TableElements.swift | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Sources/HTMLKit/Abstraction/Elements/TableElements.swift b/Sources/HTMLKit/Abstraction/Elements/TableElements.swift index 139c799d..bff14b86 100644 --- a/Sources/HTMLKit/Abstraction/Elements/TableElements.swift +++ b/Sources/HTMLKit/Abstraction/Elements/TableElements.swift @@ -3984,7 +3984,7 @@ public struct HeaderCell: ContentNode, TableElement { } } -extension HeaderCell: GlobalAttributes, GlobalEventAttributes, GlobalAccessibilityAttributes, ColumnSpanAttribute, RowSpanAttribute, HeadersAttribute, ScopeAttribute, ColumnIndexAccessibilityAttribute { +extension HeaderCell: GlobalAttributes, GlobalEventAttributes, GlobalAccessibilityAttributes, ColumnSpanAttribute, RowSpanAttribute, HeadersAttribute, ScopeAttribute, ColumnIndexAccessibilityAttribute, AbbreviatedAttribute { public func accessKey(_ value: Character) -> HeaderCell { return mutate(accesskey: .init("\(value)", context: .trusted)) @@ -4191,6 +4191,19 @@ extension HeaderCell: GlobalAttributes, GlobalEventAttributes, GlobalAccessibili return mutate(popover: .init(value.rawValue, context: .trusted)) } + @_disfavoredOverload + public func abbreviated(_ value: String) -> HeaderCell { + return mutate(abbr: .init(value, context: .tainted(.html))) + } + + public func abbreviated(_ localizedKey: LocalizedStringKey, tableName: String? = nil) -> HeaderCell { + return mutate(abbr: .init(LocalizedString(key: localizedKey, table: tableName), context: .tainted(.html))) + } + + public func abbreviated(verbatim value: String) -> HeaderCell { + return mutate(abbr: .init(value, context: .tainted(.html))) + } + public func custom(key: String, value: String, context: EscapeContext = .tainted(.html)) -> HeaderCell { return mutate(key: key, value: .init(value, context: context)) } From 3cf6eadcfbe02a48c52a88bb4c38b932ba60df64 Mon Sep 17 00:00:00 2001 From: Mattes Mohr Date: Fri, 24 Apr 2026 19:36:48 +0200 Subject: [PATCH 3/3] Add a code example --- .../Attributes/BasicAttributes.swift | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Sources/HTMLKit/Abstraction/Attributes/BasicAttributes.swift b/Sources/HTMLKit/Abstraction/Attributes/BasicAttributes.swift index ff9feb07..28410196 100644 --- a/Sources/HTMLKit/Abstraction/Attributes/BasicAttributes.swift +++ b/Sources/HTMLKit/Abstraction/Attributes/BasicAttributes.swift @@ -4166,6 +4166,13 @@ public protocol AbbreviatedAttribute: Attribute { /// Announce a brief alternate label. /// + /// ``` + /// HeaderCell { + /// "Lorem ipsum..." + /// } + /// .abbreviated("Lorem Ipsum") + /// ``` + /// /// - Parameter value: The alternate label to describe the content. /// /// - Returns: The element @@ -4173,6 +4180,13 @@ public protocol AbbreviatedAttribute: Attribute { /// Announce a brief localized alternate label. /// + /// ``` + /// HeaderCell { + /// "Lorem ipsum..." + /// } + /// .abbreviated("Lorem Ipsum") + /// ``` + /// /// - Parameters: /// - localizedKey: The string key to be translated. /// - tableName: The translation table to look in. @@ -4182,6 +4196,13 @@ public protocol AbbreviatedAttribute: Attribute { /// Announce a brief alternate label without localization. /// + /// ``` + /// HeaderCell { + /// "Lorem ipsum..." + /// } + /// .abbreviated(verbatim: "Lorem Ipsum") + /// ``` + /// /// - Parameter value: The alternate label to describe the content. /// /// - Returns: The element