@3fn/core 13.0.0 → 14.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.kiro/agents/ada-prompt.md +80 -132
- package/.kiro/agents/ada-prompt.md.attribution.json +45 -0
- package/.kiro/agents/ada.json +44 -59
- package/.kiro/agents/ada.json.attribution.json +13 -0
- package/.kiro/agents/data-prompt.md +83 -74
- package/.kiro/agents/data-prompt.md.attribution.json +53 -0
- package/.kiro/agents/data.json +31 -38
- package/.kiro/agents/data.json.attribution.json +13 -0
- package/.kiro/agents/kenya-prompt.md +83 -72
- package/.kiro/agents/kenya-prompt.md.attribution.json +53 -0
- package/.kiro/agents/kenya.json +27 -35
- package/.kiro/agents/kenya.json.attribution.json +13 -0
- package/.kiro/agents/leonardo-prompt.md +176 -234
- package/.kiro/agents/leonardo-prompt.md.attribution.json +45 -0
- package/.kiro/agents/leonardo.json +28 -36
- package/.kiro/agents/leonardo.json.attribution.json +13 -0
- package/.kiro/agents/lina-prompt.md +110 -151
- package/.kiro/agents/lina-prompt.md.attribution.json +53 -0
- package/.kiro/agents/lina.json +46 -59
- package/.kiro/agents/lina.json.attribution.json +13 -0
- package/.kiro/agents/sparky-prompt.md +89 -72
- package/.kiro/agents/sparky-prompt.md.attribution.json +53 -0
- package/.kiro/agents/sparky.json +37 -37
- package/.kiro/agents/sparky.json.attribution.json +13 -0
- package/.kiro/agents/stacy-prompt.md +74 -48
- package/.kiro/agents/stacy-prompt.md.attribution.json +45 -0
- package/.kiro/agents/stacy.json +28 -30
- package/.kiro/agents/stacy.json.attribution.json +13 -0
- package/.kiro/agents/thurgood-prompt.md +94 -138
- package/.kiro/agents/thurgood-prompt.md.attribution.json +45 -0
- package/.kiro/agents/thurgood.json +31 -35
- package/.kiro/agents/thurgood.json.attribution.json +13 -0
- package/.kiro/steering/AI-Collaboration-Principles.md +3 -3
- package/.kiro/steering/Civitas-System-Overview.md +7 -16
- package/.kiro/steering/DesignerPunk-Systems-Overview.md +6 -6
- package/.kiro/steering/Spec-Feedback-Protocol.md +2 -11
- package/.kiro/steering/Task-Completion-Protocol.md +98 -17
- package/.kiro/steering/core-goals.md +3 -3
- package/.kiro/steering/personal-note.md +1 -1
- package/.kiro/steering/start-up-tasks.md +17 -6
- package/application-mcp-server/src/index.ts +26 -0
- package/dist/ComponentTokens.android.kt +12 -12
- package/dist/ComponentTokens.ios.swift +12 -12
- package/dist/ComponentTokens.web.css +3 -3
- package/dist/DesignTokens.android.kt +1 -1
- package/dist/DesignTokens.dtcg.json +8 -5
- package/dist/DesignTokens.figma.json +2 -2
- package/dist/DesignTokens.ios.swift +1 -1
- package/dist/DesignTokens.web.css +1 -1
- package/dist/android/DesignTokens.android.kt +1 -1
- package/dist/blend/OklchBlendCalculator.js +1 -0
- package/dist/blend/ThemeAwareBlendUtilities.web.d.ts +13 -2
- package/dist/blend/ThemeAwareBlendUtilities.web.js +6 -1
- package/dist/browser/designerpunk.esm.js +36 -90
- package/dist/browser/designerpunk.esm.min.js +33 -36
- package/dist/browser/designerpunk.umd.js +36 -90
- package/dist/browser/designerpunk.umd.min.js +47 -50
- package/dist/browser/tokens.css +3 -3
- package/dist/build/tokens/defineComponentTokens.d.ts +10 -0
- package/dist/build/tokens/defineComponentTokens.js +26 -0
- package/dist/components/core/Avatar-Base/avatar.tokens.d.ts +21 -26
- package/dist/components/core/Avatar-Base/avatar.tokens.js +31 -34
- package/dist/components/core/Avatar-Base/index.d.ts +1 -1
- package/dist/components/core/Avatar-Base/index.js +2 -2
- package/dist/components/core/Avatar-Base/platforms/web/Avatar.web.js +24 -5
- package/dist/components/core/Button-CTA/examples/BasicUsage.d.ts +16 -28
- package/dist/components/core/Button-CTA/examples/BasicUsage.js +18 -43
- package/dist/components/core/Button-CTA/platforms/web/ButtonCTA.web.d.ts +3 -15
- package/dist/components/core/Button-CTA/platforms/web/ButtonCTA.web.js +9 -58
- package/dist/components/core/Button-CTA/types.d.ts +0 -24
- package/dist/components/core/Button-CTA/types.js +6 -0
- package/dist/components/core/Button-Icon/buttonIcon.tokens.d.ts +28 -14
- package/dist/components/core/Button-Icon/buttonIcon.tokens.js +35 -20
- package/dist/components/core/Input-Text-Base/types.d.ts +13 -1
- package/dist/components/core/Input-Text-Password/platforms/web/InputTextPassword.web.js +11 -2
- package/dist/generators/DTCGFormatGenerator.js +8 -0
- package/dist/generators/TokenFileGenerator.js +7 -2
- package/dist/integration/BuildErrorHandler.js +2 -2
- package/dist/ios/DesignTokens.ios.swift +1 -1
- package/dist/mcp/application-mcp.js +24 -0
- package/dist/mcp/docs-mcp.js +130 -15
- package/dist/mcp/product-mcp.js +25 -0
- package/dist/tokens/OpacityTokens.js +1 -1
- package/dist/tokens/component/progress.d.ts +65 -5
- package/dist/tokens/component/progress.js +79 -18
- package/dist/tokens/semantic/BlendTokens.d.ts +10 -3
- package/dist/tokens/semantic/BlendTokens.js +17 -5
- package/dist/tokens/semantic/OpacityTokens.d.ts +4 -4
- package/dist/tokens/semantic/OpacityTokens.js +4 -4
- package/dist/types/ComponentTypes.d.ts +1 -1
- package/dist/types/generated/TokenTypes.d.ts +1 -1
- package/dist/types/generated/TokenTypes.js +1 -1
- package/dist/validators/StemmaTokenUsageValidator.js +3 -2
- package/dist/web/DesignTokens.web.css +1 -1
- package/governance/BUILD-SYSTEM-SETUP.md +1 -2
- package/governance/Component-Development-Guide.md +23 -13
- package/governance/Component-Development-Standards.md +21 -20
- package/governance/Component-Family-Avatar.md +6 -7
- package/governance/Component-Family-Badge.md +19 -20
- package/governance/Component-Family-Button.md +30 -43
- package/governance/Component-Family-Chip.md +14 -15
- package/governance/Component-Family-Container.md +12 -13
- package/governance/Component-Family-Data-Display.md +1 -2
- package/governance/Component-Family-Divider.md +1 -2
- package/governance/Component-Family-Form-Inputs.md +65 -65
- package/governance/Component-Family-Icon.md +9 -10
- package/governance/Component-Family-Loading.md +1 -2
- package/governance/Component-Family-Modal.md +1 -2
- package/governance/Component-Family-Navigation.md +1 -2
- package/governance/Component-Family-Progress.md +0 -1
- package/governance/Component-Inheritance-Structures.md +219 -99
- package/governance/Component-MCP-Document-Template.md +6 -5
- package/governance/Component-Primitive-vs-Semantic-Philosophy.md +1 -1
- package/governance/Component-Quick-Reference.md +33 -33
- package/governance/Component-Readiness-Status.md +59 -44
- package/governance/Component-Templates.md +57 -61
- package/governance/Contract-System-Reference.md +7 -7
- package/governance/MCP-Integration-Guide.md +1 -1
- package/governance/Process-Cross-Reference-Standards.md +31 -13
- package/governance/Process-Development-Workflow.md +49 -59
- package/governance/Process-File-Organization.md +25 -28
- package/governance/Process-Hook-Operations.md +22 -11
- package/governance/Process-Orchestration-Model-Selection.md +92 -0
- package/governance/Process-Spec-Planning.md +98 -52
- package/governance/Process-Task-Type-Definitions.md +80 -4
- package/governance/Product-Handoff-Protocol.md +2 -0
- package/governance/Rosetta-System-Architecture.md +13 -11
- package/governance/Test-Behavioral-Contract-Validation.md +38 -31
- package/governance/Test-Failure-Audit-Methodology.md +1 -1
- package/governance/Token-Family-Accessibility.md +1 -2
- package/governance/Token-Family-Blend.md +18 -16
- package/governance/Token-Family-Blur.md +0 -1
- package/governance/Token-Family-Border.md +1 -2
- package/governance/Token-Family-Color.md +0 -1
- package/governance/Token-Family-Glow.md +1 -2
- package/governance/Token-Family-Layering.md +0 -1
- package/governance/Token-Family-Motion.md +1 -2
- package/governance/Token-Family-Opacity.md +0 -1
- package/governance/Token-Family-Radius.md +1 -2
- package/governance/Token-Family-Responsive.md +1 -2
- package/governance/Token-Family-Shadow.md +1 -2
- package/governance/Token-Family-Sizing.md +0 -1
- package/governance/Token-Family-Spacing.md +1 -2
- package/governance/Token-Family-Typography.md +1 -2
- package/governance/Token-Governance.md +8 -8
- package/governance/Token-Quick-Reference.md +47 -34
- package/governance/Token-Resolution-Patterns.md +1 -1
- package/governance/Token-Semantic-Structure.md +1 -1
- package/governance/Web-Authoring-Standards.md +5 -5
- package/governance/browser-distribution-guide.md +1 -4
- package/governance/classification-map.md +474 -0
- package/governance/completion-documentation-guide.md +23 -37
- package/governance/component-meta-authoring-guide.md +1 -1
- package/governance/cross-platform-vs-platform-specific-decision-framework.md +1 -1
- package/governance/platform-implementation-guidelines.md +2 -3
- package/governance/release-management-system.md +28 -63
- package/governance/rosetta-system-principles.md +8 -6
- package/governance/stemma-system-principles.md +18 -17
- package/mcp-server/src/index.ts +24 -6
- package/mcp-server/src/indexer/DocumentIndexer.ts +119 -9
- package/mcp-server/src/indexer/__tests__/bare-id-crossrefs.test.ts +250 -0
- package/mcp-server/src/indexer/cross-ref-parser.ts +29 -1
- package/mcp-server/src/indexer/index-health.ts +27 -2
- package/mcp-server/src/query/__tests__/find-docs-calibration.test.ts +11 -26
- package/mcp-server/src/relocation-integrity-gate/__tests__/relocation-integrity-gate.test.ts +72 -5
- package/mcp-server/src/relocation-integrity-gate/relocation-integrity-gate.ts +81 -24
- package/mcp-server/src/tools/list-cross-references.ts +2 -2
- package/package.json +24 -26
- package/src/__tests__/browser-distribution/css-bundling.test.ts +6 -4
- package/src/__tests__/console-allowlist.json +14 -0
- package/src/__tests__/console-fail-setup.ts +169 -0
- package/src/__tests__/integration/Spec107-DesignLanguageContext.test.ts +16 -0
- package/src/__tests__/stemma-system/behavioral-contract-validation.test.ts +70 -17
- package/src/__tests__/stemma-system/contract-catalog-name-validation.test.ts +28 -0
- package/src/__tests__/stemma-system/form-inputs-contracts.test.ts +223 -16
- package/src/__tests__/stemma-system/input-text-native-base-call-alignment.test.ts +298 -0
- package/src/blend/OklchBlendCalculator.ts +3 -0
- package/src/blend/ThemeAwareBlendUtilities.android.kt +3 -0
- package/src/blend/ThemeAwareBlendUtilities.ios.swift +3 -0
- package/src/blend/ThemeAwareBlendUtilities.web.ts +9 -1
- package/src/blend/__tests__/InteractionStateAudit.test.ts +12 -9
- package/src/build/errors/__tests__/ErrorHandler.integration.test.ts +8 -0
- package/src/build/errors/__tests__/ErrorHandler.test.ts +5 -0
- package/src/build/tokens/__tests__/defineComponentTokens.test.ts +113 -0
- package/src/build/tokens/defineComponentTokens.ts +43 -1
- package/src/build/workflow/__tests__/CICDIntegration.test.ts +12 -1
- package/src/cli/__tests__/init.test.ts +45 -11
- package/src/components/core/Avatar-Base/Avatar-Base.schema.yaml +1 -1
- package/src/components/core/Avatar-Base/__tests__/Avatar.accessibility.test.ts +121 -7
- package/src/components/core/Avatar-Base/__tests__/Avatar.image.test.ts +3 -0
- package/src/components/core/Avatar-Base/__tests__/Avatar.test.ts +15 -6
- package/src/components/core/Avatar-Base/avatar.tokens.ts +31 -34
- package/src/components/core/Avatar-Base/contracts.yaml +11 -1
- package/src/components/core/Avatar-Base/index.ts +1 -1
- package/src/components/core/Avatar-Base/platforms/web/Avatar.web.ts +24 -5
- package/src/components/core/Badge-Count-Base/contracts.yaml +1 -1
- package/src/components/core/Badge-Label-Base/contracts.yaml +1 -1
- package/src/components/core/Button-CTA/Button-CTA.schema.yaml +2 -12
- package/src/components/core/Button-CTA/README.md +3 -6
- package/src/components/core/Button-CTA/__tests__/ButtonCTA.test.ts +35 -89
- package/src/components/core/Button-CTA/__tests__/setup.test.ts +0 -2
- package/src/components/core/Button-CTA/__tests__/test-utils.ts +0 -2
- package/src/components/core/Button-CTA/contracts.yaml +6 -29
- package/src/components/core/Button-CTA/examples/BasicUsage.html +2 -14
- package/src/components/core/Button-CTA/examples/BasicUsage.tsx +17 -44
- package/src/components/core/Button-CTA/platforms/android/ButtonCTA.android.kt +12 -20
- package/src/components/core/Button-CTA/platforms/ios/ButtonCTA.ios.swift +12 -51
- package/src/components/core/Button-CTA/platforms/web/ButtonCTA.web.css +2 -26
- package/src/components/core/Button-CTA/platforms/web/ButtonCTA.web.ts +18 -71
- package/src/components/core/Button-CTA/types.ts +10 -28
- package/src/components/core/Button-Icon/buttonIcon.tokens.ts +43 -27
- package/src/components/core/Chip-Base/__tests__/ChipBase.test.ts +13 -0
- package/src/components/core/Chip-Filter/__tests__/ChipFilter.test.ts +13 -0
- package/src/components/core/Chip-Input/__tests__/ChipInput.test.ts +13 -0
- package/src/components/core/Input-Text-Base/Input-Text-Base.schema.yaml +30 -2
- package/src/components/core/Input-Text-Base/README.md +25 -2
- package/src/components/core/Input-Text-Base/__tests__/focusIndicators.test.ts +16 -15
- package/src/components/core/Input-Text-Base/contracts.yaml +90 -0
- package/src/components/core/Input-Text-Base/platforms/android/InputTextBase.android.kt +26 -12
- package/src/components/core/Input-Text-Base/platforms/ios/InputTextBase.ios.swift +195 -59
- package/src/components/core/Input-Text-Base/types.ts +13 -1
- package/src/components/core/Input-Text-Email/Input-Text-Email.schema.yaml +5 -1
- package/src/components/core/Input-Text-Email/README.md +8 -7
- package/src/components/core/Input-Text-Email/platforms/android/InputTextEmail.android.kt +1 -4
- package/src/components/core/Input-Text-Email/platforms/ios/InputTextEmail.ios.swift +2 -16
- package/src/components/core/Input-Text-Password/Input-Text-Password.schema.yaml +10 -3
- package/src/components/core/Input-Text-Password/README.md +9 -8
- package/src/components/core/Input-Text-Password/contracts.yaml +5 -0
- package/src/components/core/Input-Text-Password/platforms/android/InputTextPassword.android.kt +17 -7
- package/src/components/core/Input-Text-Password/platforms/ios/InputTextPassword.ios.swift +22 -20
- package/src/components/core/Input-Text-Password/platforms/web/InputTextPassword.web.ts +11 -2
- package/src/components/core/Input-Text-PhoneNumber/Input-Text-PhoneNumber.schema.yaml +5 -1
- package/src/components/core/Input-Text-PhoneNumber/README.md +9 -8
- package/src/components/core/Input-Text-PhoneNumber/platforms/android/InputTextPhoneNumber.android.kt +2 -5
- package/src/components/core/Input-Text-PhoneNumber/platforms/ios/InputTextPhoneNumber.ios.swift +3 -17
- package/src/components/core/Nav-Header-App/contracts.yaml +1 -1
- package/src/components/core/Nav-SegmentedChoice-Base/contracts.yaml +1 -1
- package/src/components/core/Progress-Indicator-Connector-Base/contracts.yaml +1 -1
- package/src/components/core/Progress-Indicator-Label-Base/contracts.yaml +1 -1
- package/src/components/core/Progress-Indicator-Node-Base/contracts.yaml +1 -1
- package/src/components/core/Progress-Stepper-Base/__tests__/StepperBase.test.ts +5 -2
- package/src/components/core/Progress-Stepper-Detailed/__tests__/StepperDetailed.test.ts +5 -2
- package/src/generators/DTCGFormatGenerator.ts +6 -0
- package/src/generators/TokenFileGenerator.ts +7 -2
- package/src/generators/__tests__/DTCGConfigOptions.test.ts +14 -5
- package/src/integration/BuildErrorHandler.ts +2 -2
- package/src/tokens/OpacityTokens.ts +1 -1
- package/src/tokens/__tests__/OpacityTokens.test.ts +3 -1
- package/src/tokens/__tests__/ProgressTokenCompliance.test.ts +5 -3
- package/src/tokens/__tests__/ProgressTokenFormulas.test.ts +11 -11
- package/src/tokens/__tests__/ProgressTokenTranslation.test.ts +22 -20
- package/src/tokens/component/progress.ts +83 -21
- package/src/tokens/semantic/BlendTokens.ts +26 -5
- package/src/tokens/semantic/OpacityTokens.ts +4 -4
- package/src/types/ComponentTypes.ts +1 -1
- package/src/types/generated/TokenTypes.ts +1 -1
- package/src/validators/StemmaTokenUsageValidator.ts +3 -2
- package/token-index/components.yaml +8 -8
- package/token-index/semantics.yaml +1 -2
- package/src/tools/release/__tests__/ChangeClassifier.test.ts +0 -133
- package/src/tools/release/__tests__/ChangeExtractor.test.ts +0 -222
- package/src/tools/release/__tests__/GitHubPublisher.test.ts +0 -240
- package/src/tools/release/__tests__/NotesRenderer.test.ts +0 -142
- package/src/tools/release/__tests__/NpmPublisher.test.ts +0 -289
- package/src/tools/release/__tests__/PipelineIntegration.test.ts +0 -188
- package/src/tools/release/__tests__/ReleasePipeline.test.ts +0 -192
- package/src/tools/release/__tests__/SemanticVersionValidator.test.ts +0 -49
- package/src/tools/release/__tests__/SummaryScanner.test.ts +0 -141
- package/src/tools/release/__tests__/TagResolver.test.ts +0 -91
- package/src/tools/release/__tests__/VersionCalculator.test.ts +0 -270
- package/src/tools/release/__tests__/helpers/NpmMockHelper.ts +0 -80
- package/src/tools/release/cli/ReleasePipeline.ts +0 -165
- package/src/tools/release/cli/release-tool.ts +0 -107
- package/src/tools/release/pipeline/ChangeClassifier.ts +0 -61
- package/src/tools/release/pipeline/ChangeExtractor.ts +0 -87
- package/src/tools/release/pipeline/NotesRenderer.ts +0 -66
- package/src/tools/release/pipeline/SummaryScanner.ts +0 -70
- package/src/tools/release/pipeline/TagResolver.ts +0 -40
- package/src/tools/release/pipeline/VersionCalculator.ts +0 -375
- package/src/tools/release/publishers/GitHubPublisher.ts +0 -228
- package/src/tools/release/publishers/NpmPublisher.ts +0 -196
- package/src/tools/release/release-config.json +0 -5
- package/src/tools/release/types/index.ts +0 -282
- package/src/tools/release/validators/SemanticVersionValidator.ts +0 -67
|
@@ -13,21 +13,33 @@
|
|
|
13
13
|
* - Color animation (text.subtle → primary with blend.focusSaturate)
|
|
14
14
|
* - Offset animation (translateY)
|
|
15
15
|
* - Trailing icon support (error, success, info)
|
|
16
|
+
* - Trailing content slot for semantic variants (e.g., password toggle)
|
|
17
|
+
* - Read-only rendering (B-prime): selectable Text inside the shared field
|
|
18
|
+
* chrome — never disabled semantics
|
|
16
19
|
* - Respects accessibilityReduceMotion
|
|
17
20
|
* - WCAG 2.1 AA compliant
|
|
18
|
-
* - Uses theme-aware blend utilities for state colors (focus
|
|
19
|
-
*
|
|
21
|
+
* - Uses theme-aware blend utilities for state colors (focus)
|
|
22
|
+
*
|
|
20
23
|
* Behavioral Contracts:
|
|
21
|
-
* -
|
|
22
|
-
*
|
|
23
|
-
* -
|
|
24
|
-
* -
|
|
25
|
-
* -
|
|
26
|
-
* -
|
|
27
|
-
* -
|
|
28
|
-
* -
|
|
29
|
-
*
|
|
30
|
-
*
|
|
24
|
+
* - interaction_focusable: Can receive keyboard focus (iOS readOnly carve-out
|
|
25
|
+
* declared in contracts.yaml — readOnly content is not in FKA focus order)
|
|
26
|
+
* - content_float_label: Label animates on focus
|
|
27
|
+
* - validation_on_blur: Validation triggers on blur
|
|
28
|
+
* - state_error: Shows error message and styling
|
|
29
|
+
* - state_success: Shows success styling
|
|
30
|
+
* - state_readonly: Read-only display — selectable/copyable, non-editable,
|
|
31
|
+
* announced read-only, never a disabled/dimmed trait (concept ballot
|
|
32
|
+
* pending ratification; see contracts.yaml)
|
|
33
|
+
* - content_trailing_icon: Shows contextual trailing icons
|
|
34
|
+
* - interaction_focus_ring: WCAG 2.4.7 focus visible indicator
|
|
35
|
+
* - accessibility_reduced_motion: Respects prefers-reduced-motion
|
|
36
|
+
*
|
|
37
|
+
* Read-only (B-prime ruling — Peter, 2026-07-15,
|
|
38
|
+
* .kiro/issues/input-text-base-ios-readonly-adjudication.md):
|
|
39
|
+
* readOnly renders as `Text(value).textSelection(.enabled)` inside the same
|
|
40
|
+
* field chrome as the editable field. The SwiftUI disabled modifier is never
|
|
41
|
+
* applied. onFocus/onBlur are DECLARED not-fired on the iOS readOnly path.
|
|
42
|
+
*
|
|
31
43
|
* Requirements: 1.1, 1.2, 1.3, 1.5, 4.1, 4.2, 4.3, 8.1, 8.2, 8.4, 11.1, 11.2, 11.3, 13.1
|
|
32
44
|
*/
|
|
33
45
|
|
|
@@ -81,7 +93,9 @@ struct InputTextBase: View {
|
|
|
81
93
|
/// Placeholder text (only shown when label is floated and input is empty)
|
|
82
94
|
let placeholder: String?
|
|
83
95
|
|
|
84
|
-
/// Read-only state
|
|
96
|
+
/// Read-only state (state_readonly contract): renders the value as
|
|
97
|
+
/// selectable Text inside the field chrome — never disabled semantics.
|
|
98
|
+
/// Ignored for secure fields (see isReadOnlyDisplay).
|
|
85
99
|
let readOnly: Bool
|
|
86
100
|
|
|
87
101
|
/// Required field indicator
|
|
@@ -89,10 +103,12 @@ struct InputTextBase: View {
|
|
|
89
103
|
|
|
90
104
|
/// Maximum length for input value
|
|
91
105
|
let maxLength: Int?
|
|
92
|
-
|
|
93
|
-
///
|
|
94
|
-
|
|
95
|
-
|
|
106
|
+
|
|
107
|
+
/// Trailing content slot rendered after the status icons
|
|
108
|
+
/// (e.g., password visibility toggle). Declared as `var` with a
|
|
109
|
+
/// default so the memberwise initializer keeps it optional and last.
|
|
110
|
+
var trailingContent: AnyView? = nil
|
|
111
|
+
|
|
96
112
|
// MARK: - State
|
|
97
113
|
|
|
98
114
|
/// Whether input currently has focus
|
|
@@ -112,6 +128,22 @@ struct InputTextBase: View {
|
|
|
112
128
|
private var isFilled: Bool {
|
|
113
129
|
!value.isEmpty
|
|
114
130
|
}
|
|
131
|
+
|
|
132
|
+
/// Whether to render the read-only display path (B-prime).
|
|
133
|
+
///
|
|
134
|
+
/// SECURITY-CRITICAL GATE: the read-only path renders the value as
|
|
135
|
+
/// selectable plaintext `Text`. It MUST be unreachable for secure fields
|
|
136
|
+
/// (`type == .password`), or a readOnly password would render its secret
|
|
137
|
+
/// in plaintext. readOnly is contracted OUT of Input-Text-Password
|
|
138
|
+
/// entirely (adjudication condition 4:
|
|
139
|
+
/// .kiro/issues/input-text-base-ios-readonly-adjudication.md — RULED
|
|
140
|
+
/// B-prime, Peter 2026-07-15); the Password variant additionally strips
|
|
141
|
+
/// readOnly before it reaches this component. On a secure field, readOnly
|
|
142
|
+
/// is ignored (the field stays an editable SecureField) rather than ever
|
|
143
|
+
/// routing to the plaintext Text branch.
|
|
144
|
+
private var isReadOnlyDisplay: Bool {
|
|
145
|
+
readOnly && type != .password
|
|
146
|
+
}
|
|
115
147
|
|
|
116
148
|
/// Whether input has error
|
|
117
149
|
private var hasError: Bool {
|
|
@@ -140,8 +172,6 @@ struct InputTextBase: View {
|
|
|
140
172
|
return theme.colorFeedbackErrorText
|
|
141
173
|
} else if isSuccess {
|
|
142
174
|
return theme.colorFeedbackSuccessText
|
|
143
|
-
} else if isDisabled {
|
|
144
|
-
return theme.colorActionPrimary.disabledBlend()
|
|
145
175
|
} else if isFocused {
|
|
146
176
|
return theme.colorActionPrimary.focusBlend()
|
|
147
177
|
} else {
|
|
@@ -164,8 +194,6 @@ struct InputTextBase: View {
|
|
|
164
194
|
return theme.colorFeedbackErrorText
|
|
165
195
|
} else if isSuccess {
|
|
166
196
|
return theme.colorFeedbackSuccessText
|
|
167
|
-
} else if isDisabled {
|
|
168
|
-
return theme.colorActionPrimary.disabledBlend()
|
|
169
197
|
} else if isFocused {
|
|
170
198
|
return theme.colorActionPrimary.focusBlend()
|
|
171
199
|
} else {
|
|
@@ -213,18 +241,45 @@ struct InputTextBase: View {
|
|
|
213
241
|
}
|
|
214
242
|
|
|
215
243
|
// Input field
|
|
216
|
-
if
|
|
244
|
+
if isReadOnlyDisplay {
|
|
245
|
+
// Read-only rendering (B-prime): selectable Text inside
|
|
246
|
+
// the shared field chrome. Never the SwiftUI disabled
|
|
247
|
+
// modifier — it produces disabled semantics (unfocusable,
|
|
248
|
+
// uncopyable, VoiceOver "dimmed"), which DesignerPunk bans.
|
|
249
|
+
// Long-press → select/copy (whole-string copy is the
|
|
250
|
+
// contracted iOS behavior). No keyboard is raised.
|
|
251
|
+
//
|
|
252
|
+
// Declared behavior (state_readonly contract):
|
|
253
|
+
// - NOT in keyboard/FKA focus order (mitigable declared
|
|
254
|
+
// exception — .focusable(interactions:) prototype
|
|
255
|
+
// tracked; contract tightens if it verifies on-device)
|
|
256
|
+
// - onFocus/onBlur DECLARED not-fired on this path
|
|
257
|
+
// (there is no focus system here — nothing simulates
|
|
258
|
+
// or pretends otherwise)
|
|
259
|
+
// - Empty value: label rests in placeholder position
|
|
260
|
+
// (cross-platform parity); the chrome reserves full
|
|
261
|
+
// height unconditionally.
|
|
262
|
+
Text(value)
|
|
263
|
+
.textSelection(.enabled)
|
|
264
|
+
.modifier(InputTextBaseFieldChrome(
|
|
265
|
+
borderColor: borderColor,
|
|
266
|
+
isFocused: false,
|
|
267
|
+
hasTrailingIcon: showErrorIcon || showSuccessIcon || showInfoIconVisible || trailingContent != nil
|
|
268
|
+
))
|
|
269
|
+
} else if type == .password {
|
|
270
|
+
// Secure path: readOnly is IGNORED here (see
|
|
271
|
+
// isReadOnlyDisplay) — a secure field never routes to
|
|
272
|
+
// the plaintext read-only Text branch, and disabled
|
|
273
|
+
// semantics are never applied.
|
|
217
274
|
SecureField(isLabelFloated && placeholder != nil ? placeholder! : "", text: $value)
|
|
218
275
|
.textFieldStyle(InputTextBaseFieldStyle(
|
|
219
276
|
borderColor: borderColor,
|
|
220
277
|
isFocused: isFocused,
|
|
221
278
|
hasError: hasError,
|
|
222
279
|
isSuccess: isSuccess,
|
|
223
|
-
|
|
224
|
-
hasTrailingIcon: showErrorIcon || showSuccessIcon || showInfoIconVisible
|
|
280
|
+
hasTrailingIcon: showErrorIcon || showSuccessIcon || showInfoIconVisible || trailingContent != nil
|
|
225
281
|
))
|
|
226
282
|
.focused($isFocused)
|
|
227
|
-
.disabled(readOnly || isDisabled)
|
|
228
283
|
.textContentType(autocomplete)
|
|
229
284
|
.onChange(of: value) { newValue in
|
|
230
285
|
if let maxLength = maxLength, newValue.count > maxLength {
|
|
@@ -246,11 +301,9 @@ struct InputTextBase: View {
|
|
|
246
301
|
isFocused: isFocused,
|
|
247
302
|
hasError: hasError,
|
|
248
303
|
isSuccess: isSuccess,
|
|
249
|
-
|
|
250
|
-
hasTrailingIcon: showErrorIcon || showSuccessIcon || showInfoIconVisible
|
|
304
|
+
hasTrailingIcon: showErrorIcon || showSuccessIcon || showInfoIconVisible || trailingContent != nil
|
|
251
305
|
))
|
|
252
306
|
.focused($isFocused)
|
|
253
|
-
.disabled(readOnly || isDisabled)
|
|
254
307
|
.textContentType(autocomplete)
|
|
255
308
|
.keyboardType(keyboardTypeForInputType(type))
|
|
256
309
|
.onChange(of: value) { newValue in
|
|
@@ -295,6 +348,12 @@ struct InputTextBase: View {
|
|
|
295
348
|
value: showInfoIconVisible
|
|
296
349
|
)
|
|
297
350
|
}
|
|
351
|
+
|
|
352
|
+
// Trailing content slot (e.g., password visibility toggle)
|
|
353
|
+
if let trailingContent = trailingContent {
|
|
354
|
+
trailingContent
|
|
355
|
+
.padding(.trailing, DesignTokens.spaceInset100)
|
|
356
|
+
}
|
|
298
357
|
}
|
|
299
358
|
.frame(minHeight: DesignTokens.tapAreaRecommended)
|
|
300
359
|
|
|
@@ -319,7 +378,25 @@ struct InputTextBase: View {
|
|
|
319
378
|
.accessibilityElement(children: .contain)
|
|
320
379
|
.accessibilityLabel(label)
|
|
321
380
|
.accessibilityValue(value)
|
|
322
|
-
.accessibilityHint(
|
|
381
|
+
.accessibilityHint(accessibilityHintText)
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/// Composed accessibility hint.
|
|
385
|
+
///
|
|
386
|
+
/// On the read-only path this composes the read-only indication with any
|
|
387
|
+
/// helper text, so VoiceOver announces label + value + "Read only"
|
|
388
|
+
/// (state_readonly contract) — never a disabled/dimmed trait. iOS has no
|
|
389
|
+
/// read-only accessibility trait; the hint is the correct announcement
|
|
390
|
+
/// route (adjudication, Kenya R1).
|
|
391
|
+
private var accessibilityHintText: String {
|
|
392
|
+
var parts: [String] = []
|
|
393
|
+
if isReadOnlyDisplay {
|
|
394
|
+
parts.append("Read only")
|
|
395
|
+
}
|
|
396
|
+
if let helperText = helperText, !helperText.isEmpty {
|
|
397
|
+
parts.append(helperText)
|
|
398
|
+
}
|
|
399
|
+
return parts.joined(separator: ". ")
|
|
323
400
|
}
|
|
324
401
|
|
|
325
402
|
// MARK: - Helper Methods
|
|
@@ -353,30 +430,48 @@ enum InputType {
|
|
|
353
430
|
case url
|
|
354
431
|
}
|
|
355
432
|
|
|
356
|
-
// MARK: -
|
|
433
|
+
// MARK: - Shared Field Chrome
|
|
357
434
|
|
|
358
435
|
/**
|
|
359
|
-
*
|
|
436
|
+
* Shared field chrome (B-prime chrome extraction, adjudication 2026-07-15).
|
|
437
|
+
*
|
|
438
|
+
* The visual treatment of the field box — typography, text color, insets,
|
|
439
|
+
* canvas background, border overlay, and focus-ring overlay — extracted from
|
|
440
|
+
* InputTextBaseFieldStyle so it applies to BOTH rendering paths: the editable
|
|
441
|
+
* TextField/SecureField (via InputTextBaseFieldStyle, which delegates here)
|
|
442
|
+
* and the read-only Text (applied directly). One chrome, two contents — the
|
|
443
|
+
* two paths cannot drift visually.
|
|
360
444
|
*/
|
|
361
|
-
struct
|
|
445
|
+
struct InputTextBaseFieldChrome: ViewModifier {
|
|
362
446
|
let borderColor: Color
|
|
363
447
|
let isFocused: Bool
|
|
364
|
-
let hasError: Bool
|
|
365
|
-
let isSuccess: Bool
|
|
366
|
-
let isDisabled: Bool
|
|
367
448
|
let hasTrailingIcon: Bool
|
|
368
|
-
|
|
449
|
+
|
|
369
450
|
@Environment(\.accessibilityReduceMotion) var reduceMotion
|
|
370
451
|
@Environment(\.dpTheme) private var theme
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
452
|
+
|
|
453
|
+
/// Minimum content height, reserved UNCONDITIONALLY on both paths:
|
|
454
|
+
/// input line height (fontSize × unitless lineHeight multiplier, the
|
|
455
|
+
/// Badge/Nav token-derived pattern) + vertical insets. This deliberately
|
|
456
|
+
/// does NOT reason about empty `Text` intrinsic size (undocumented, has
|
|
457
|
+
/// varied across SwiftUI releases): an empty readOnly value keeps the
|
|
458
|
+
/// same field height as a filled editable field. Note: the field uses a
|
|
459
|
+
/// fixed token font size (no Dynamic Type) today — if the family adopts
|
|
460
|
+
/// Dynamic Type, this minHeight must scale with it.
|
|
461
|
+
private var minFieldHeight: CGFloat {
|
|
462
|
+
(DesignTokens.typographyInput.fontSize * DesignTokens.typographyInput.lineHeight)
|
|
463
|
+
+ (DesignTokens.spaceInset100 * 2)
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
func body(content: Content) -> some View {
|
|
467
|
+
content
|
|
374
468
|
.font(Font.system(size: DesignTokens.typographyInput.fontSize)
|
|
375
469
|
.weight(DesignTokens.typographyInput.fontWeight))
|
|
376
|
-
.foregroundColor(
|
|
470
|
+
.foregroundColor(theme.colorTextDefault)
|
|
377
471
|
.padding(.leading, DesignTokens.spaceInset100)
|
|
378
472
|
.padding(.vertical, DesignTokens.spaceInset100)
|
|
379
473
|
.padding(.trailing, hasTrailingIcon ? 0 : DesignTokens.spaceInset100)
|
|
474
|
+
.frame(maxWidth: .infinity, minHeight: minFieldHeight, alignment: .leading)
|
|
380
475
|
.background(theme.colorStructureCanvas)
|
|
381
476
|
.overlay(
|
|
382
477
|
RoundedRectangle(cornerRadius: DesignTokens.radius150)
|
|
@@ -386,7 +481,7 @@ struct InputTextBaseFieldStyle: TextFieldStyle {
|
|
|
386
481
|
RoundedRectangle(cornerRadius: DesignTokens.radius150)
|
|
387
482
|
.stroke(theme.colorActionPrimary, lineWidth: DesignTokens.accessibilityFocusWidth)
|
|
388
483
|
.padding(-DesignTokens.accessibilityFocusOffset)
|
|
389
|
-
.opacity(isFocused
|
|
484
|
+
.opacity(isFocused ? 1 : 0)
|
|
390
485
|
.animation(
|
|
391
486
|
reduceMotion ? .none : Animation.timingCurve(0.4, 0.0, 0.2, 1.0, duration: DesignTokens.MotionFocusTransition.duration),
|
|
392
487
|
value: isFocused
|
|
@@ -395,6 +490,30 @@ struct InputTextBaseFieldStyle: TextFieldStyle {
|
|
|
395
490
|
}
|
|
396
491
|
}
|
|
397
492
|
|
|
493
|
+
// MARK: - Custom Text Field Style
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Custom text field style for consistent appearance.
|
|
497
|
+
* Delegates all visual treatment to InputTextBaseFieldChrome (shared with
|
|
498
|
+
* the read-only rendering path).
|
|
499
|
+
*/
|
|
500
|
+
struct InputTextBaseFieldStyle: TextFieldStyle {
|
|
501
|
+
let borderColor: Color
|
|
502
|
+
let isFocused: Bool
|
|
503
|
+
let hasError: Bool
|
|
504
|
+
let isSuccess: Bool
|
|
505
|
+
let hasTrailingIcon: Bool
|
|
506
|
+
|
|
507
|
+
func _body(configuration: TextField<Self._Label>) -> some View {
|
|
508
|
+
configuration
|
|
509
|
+
.modifier(InputTextBaseFieldChrome(
|
|
510
|
+
borderColor: borderColor,
|
|
511
|
+
isFocused: isFocused,
|
|
512
|
+
hasTrailingIcon: hasTrailingIcon
|
|
513
|
+
))
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
398
517
|
// MARK: - Preview
|
|
399
518
|
|
|
400
519
|
#if DEBUG
|
|
@@ -417,8 +536,7 @@ struct InputTextBase_Previews: PreviewProvider {
|
|
|
417
536
|
placeholder: nil,
|
|
418
537
|
readOnly: false,
|
|
419
538
|
required: false,
|
|
420
|
-
maxLength: nil
|
|
421
|
-
isDisabled: false
|
|
539
|
+
maxLength: nil
|
|
422
540
|
)
|
|
423
541
|
|
|
424
542
|
InputTextBase(
|
|
@@ -437,8 +555,7 @@ struct InputTextBase_Previews: PreviewProvider {
|
|
|
437
555
|
placeholder: nil,
|
|
438
556
|
readOnly: false,
|
|
439
557
|
required: false,
|
|
440
|
-
maxLength: nil
|
|
441
|
-
isDisabled: false
|
|
558
|
+
maxLength: nil
|
|
442
559
|
)
|
|
443
560
|
|
|
444
561
|
InputTextBase(
|
|
@@ -457,8 +574,7 @@ struct InputTextBase_Previews: PreviewProvider {
|
|
|
457
574
|
placeholder: nil,
|
|
458
575
|
readOnly: false,
|
|
459
576
|
required: true,
|
|
460
|
-
maxLength: nil
|
|
461
|
-
isDisabled: false
|
|
577
|
+
maxLength: nil
|
|
462
578
|
)
|
|
463
579
|
|
|
464
580
|
InputTextBase(
|
|
@@ -477,28 +593,48 @@ struct InputTextBase_Previews: PreviewProvider {
|
|
|
477
593
|
placeholder: nil,
|
|
478
594
|
readOnly: false,
|
|
479
595
|
required: false,
|
|
480
|
-
maxLength: nil
|
|
481
|
-
isDisabled: false
|
|
596
|
+
maxLength: nil
|
|
482
597
|
)
|
|
483
|
-
|
|
598
|
+
|
|
599
|
+
// Read-only (B-prime): selectable Text in the shared chrome
|
|
484
600
|
InputTextBase(
|
|
485
|
-
id: "preview-
|
|
486
|
-
label: "
|
|
487
|
-
value: .constant("
|
|
601
|
+
id: "preview-read-only",
|
|
602
|
+
label: "Reference number",
|
|
603
|
+
value: .constant("REF-2026-07-15"),
|
|
488
604
|
onChange: nil,
|
|
489
605
|
onFocus: nil,
|
|
490
606
|
onBlur: nil,
|
|
491
|
-
helperText: "
|
|
607
|
+
helperText: "Long-press to copy",
|
|
492
608
|
errorMessage: nil,
|
|
493
609
|
isSuccess: false,
|
|
494
610
|
showInfoIcon: false,
|
|
495
|
-
type: .
|
|
496
|
-
autocomplete:
|
|
611
|
+
type: .text,
|
|
612
|
+
autocomplete: nil,
|
|
497
613
|
placeholder: nil,
|
|
498
|
-
readOnly:
|
|
614
|
+
readOnly: true,
|
|
615
|
+
required: false,
|
|
616
|
+
maxLength: nil
|
|
617
|
+
)
|
|
618
|
+
|
|
619
|
+
// Read-only, empty: label rests in placeholder position,
|
|
620
|
+
// chrome reserves full field height
|
|
621
|
+
InputTextBase(
|
|
622
|
+
id: "preview-read-only-empty",
|
|
623
|
+
label: "Reference number",
|
|
624
|
+
value: .constant(""),
|
|
625
|
+
onChange: nil,
|
|
626
|
+
onFocus: nil,
|
|
627
|
+
onBlur: nil,
|
|
628
|
+
helperText: nil,
|
|
629
|
+
errorMessage: nil,
|
|
630
|
+
isSuccess: false,
|
|
631
|
+
showInfoIcon: false,
|
|
632
|
+
type: .text,
|
|
633
|
+
autocomplete: nil,
|
|
634
|
+
placeholder: nil,
|
|
635
|
+
readOnly: true,
|
|
499
636
|
required: false,
|
|
500
|
-
maxLength: nil
|
|
501
|
-
isDisabled: true
|
|
637
|
+
maxLength: nil
|
|
502
638
|
)
|
|
503
639
|
}
|
|
504
640
|
.padding()
|
|
@@ -54,7 +54,19 @@ export interface InputTextBaseProps {
|
|
|
54
54
|
/** Placeholder text (only shown when label is floated and input is empty) */
|
|
55
55
|
placeholder?: string;
|
|
56
56
|
|
|
57
|
-
/**
|
|
57
|
+
/**
|
|
58
|
+
* Read-only state (state_readonly contract): the value is displayed
|
|
59
|
+
* non-editably while remaining perceivable, selectable, and copyable —
|
|
60
|
+
* never rendered with disabled semantics on any platform.
|
|
61
|
+
*
|
|
62
|
+
* Platform note: web and Android readOnly fields participate in keyboard
|
|
63
|
+
* focus order; iOS renders readOnly as selectable static text in the
|
|
64
|
+
* field chrome and is not in focus order (declared carve-out — see
|
|
65
|
+
* contracts.yaml interaction_focusable).
|
|
66
|
+
*
|
|
67
|
+
* NOT supported by Input-Text-Password: readOnly is contracted out of the
|
|
68
|
+
* Password variant (see its contracts.yaml excludes) and is ignored there.
|
|
69
|
+
*/
|
|
58
70
|
readOnly?: boolean;
|
|
59
71
|
|
|
60
72
|
/** Required field indicator */
|
|
@@ -101,7 +101,11 @@ properties:
|
|
|
101
101
|
type: boolean
|
|
102
102
|
required: false
|
|
103
103
|
default: false
|
|
104
|
-
description:
|
|
104
|
+
description: >-
|
|
105
|
+
Read-only display state (state_readonly contract) — the value is
|
|
106
|
+
non-editable but stays perceivable, selectable, and copyable; no
|
|
107
|
+
keyboard/IME is raised and disabled semantics are never applied. iOS
|
|
108
|
+
renders it as selectable static text inside the field chrome.
|
|
105
109
|
|
|
106
110
|
required:
|
|
107
111
|
type: boolean
|
|
@@ -97,7 +97,7 @@ InputTextEmail(
|
|
|
97
97
|
| `isSuccess` | boolean | No | false | Success state indicator |
|
|
98
98
|
| `showInfoIcon` | boolean | No | false | Show info icon |
|
|
99
99
|
| `placeholder` | string | No | - | Placeholder text |
|
|
100
|
-
| `readOnly` | boolean | No | false | Read-only state |
|
|
100
|
+
| `readOnly` | boolean | No | false | Read-only display state (`state_readonly` contract): value is non-editable but selectable and copyable; no keyboard raised; iOS renders selectable text in the field chrome |
|
|
101
101
|
| `required` | boolean | No | false | Required field indicator |
|
|
102
102
|
| `maxLength` | number | No | - | Maximum input length |
|
|
103
103
|
| `testID` | string | No | - | Test ID for automation |
|
|
@@ -126,15 +126,16 @@ InputTextEmail(
|
|
|
126
126
|
3. **validates_on_blur**: Validation triggers on blur
|
|
127
127
|
4. **error_state_display**: Shows error message and styling
|
|
128
128
|
5. **success_state_display**: Shows success styling
|
|
129
|
-
6. **
|
|
130
|
-
7. **
|
|
131
|
-
8. **
|
|
132
|
-
|
|
129
|
+
6. **trailing_icon_display**: Shows contextual trailing icons
|
|
130
|
+
7. **focus_ring**: WCAG 2.4.7 focus visible indicator
|
|
131
|
+
8. **reduced_motion_support**: Respects prefers-reduced-motion
|
|
132
|
+
|
|
133
|
+
**Excluded**: `state_disabled` — DesignerPunk does not support disabled states for usability and accessibility reasons. If an action is unavailable, the component should not be rendered (adjudicated 2026-07-15).
|
|
133
134
|
|
|
134
135
|
### Email-Specific Contracts
|
|
135
136
|
|
|
136
|
-
|
|
137
|
-
|
|
137
|
+
9. **validates_email_format**: Validates email against RFC 5322 pattern
|
|
138
|
+
10. **provides_email_autocomplete**: Enables browser/platform email autofill
|
|
138
139
|
|
|
139
140
|
## Validation
|
|
140
141
|
|
|
@@ -78,7 +78,6 @@ private fun isValidEmail(email: String): Boolean {
|
|
|
78
78
|
* @param maxLength Maximum length for input value
|
|
79
79
|
* @param imeAction IME action for keyboard
|
|
80
80
|
* @param keyboardActions Keyboard actions
|
|
81
|
-
* @param isDisabled Disabled state
|
|
82
81
|
* @param invalidEmailMessage Custom invalid email message
|
|
83
82
|
* @param customValidator Custom email validator function
|
|
84
83
|
*/
|
|
@@ -102,7 +101,6 @@ fun InputTextEmail(
|
|
|
102
101
|
maxLength: Int? = null,
|
|
103
102
|
imeAction: ImeAction = ImeAction.Done,
|
|
104
103
|
keyboardActions: KeyboardActions = KeyboardActions.Default,
|
|
105
|
-
isDisabled: Boolean = false,
|
|
106
104
|
invalidEmailMessage: String = DEFAULT_INVALID_EMAIL_MESSAGE,
|
|
107
105
|
customValidator: ((String) -> Boolean)? = null
|
|
108
106
|
) {
|
|
@@ -158,8 +156,7 @@ fun InputTextEmail(
|
|
|
158
156
|
required = required,
|
|
159
157
|
maxLength = maxLength,
|
|
160
158
|
imeAction = imeAction,
|
|
161
|
-
keyboardActions = keyboardActions
|
|
162
|
-
isDisabled = isDisabled
|
|
159
|
+
keyboardActions = keyboardActions
|
|
163
160
|
)
|
|
164
161
|
}
|
|
165
162
|
|
|
@@ -104,10 +104,7 @@ struct InputTextEmail: View {
|
|
|
104
104
|
|
|
105
105
|
/// Maximum length for input value
|
|
106
106
|
let maxLength: Int?
|
|
107
|
-
|
|
108
|
-
/// Disabled state
|
|
109
|
-
let isDisabled: Bool
|
|
110
|
-
|
|
107
|
+
|
|
111
108
|
/// Custom invalid email message
|
|
112
109
|
let invalidEmailMessage: String
|
|
113
110
|
|
|
@@ -155,7 +152,6 @@ struct InputTextEmail: View {
|
|
|
155
152
|
readOnly: Bool = false,
|
|
156
153
|
required: Bool = false,
|
|
157
154
|
maxLength: Int? = nil,
|
|
158
|
-
isDisabled: Bool = false,
|
|
159
155
|
invalidEmailMessage: String = defaultInvalidEmailMessage,
|
|
160
156
|
customValidator: ((String) -> Bool)? = nil
|
|
161
157
|
) {
|
|
@@ -174,7 +170,6 @@ struct InputTextEmail: View {
|
|
|
174
170
|
self.readOnly = readOnly
|
|
175
171
|
self.required = required
|
|
176
172
|
self.maxLength = maxLength
|
|
177
|
-
self.isDisabled = isDisabled
|
|
178
173
|
self.invalidEmailMessage = invalidEmailMessage
|
|
179
174
|
self.customValidator = customValidator
|
|
180
175
|
}
|
|
@@ -207,8 +202,7 @@ struct InputTextEmail: View {
|
|
|
207
202
|
placeholder: placeholder,
|
|
208
203
|
readOnly: readOnly,
|
|
209
204
|
required: required,
|
|
210
|
-
maxLength: maxLength
|
|
211
|
-
isDisabled: isDisabled
|
|
205
|
+
maxLength: maxLength
|
|
212
206
|
)
|
|
213
207
|
}
|
|
214
208
|
|
|
@@ -268,14 +262,6 @@ struct InputTextEmail_Previews: PreviewProvider {
|
|
|
268
262
|
isSuccess: true
|
|
269
263
|
)
|
|
270
264
|
|
|
271
|
-
InputTextEmail(
|
|
272
|
-
id: "preview-disabled",
|
|
273
|
-
label: "Email",
|
|
274
|
-
value: .constant("user@example.com"),
|
|
275
|
-
helperText: "This field is disabled",
|
|
276
|
-
isDisabled: true
|
|
277
|
-
)
|
|
278
|
-
|
|
279
265
|
InputTextEmail(
|
|
280
266
|
id: "preview-required",
|
|
281
267
|
label: "Email",
|
|
@@ -103,7 +103,12 @@ properties:
|
|
|
103
103
|
type: boolean
|
|
104
104
|
required: false
|
|
105
105
|
default: false
|
|
106
|
-
description:
|
|
106
|
+
description: >-
|
|
107
|
+
Accepted for API stability but IGNORED — readOnly is contracted out of
|
|
108
|
+
Input-Text-Password (contracts.yaml excludes.state_readonly; adjudication
|
|
109
|
+
.kiro/issues/input-text-base-ios-readonly-adjudication.md, RULED B-prime,
|
|
110
|
+
Peter 2026-07-15). Implementations never forward it to the base on any
|
|
111
|
+
platform.
|
|
107
112
|
|
|
108
113
|
required:
|
|
109
114
|
type: boolean
|
|
@@ -262,13 +267,15 @@ platform_notes:
|
|
|
262
267
|
ios:
|
|
263
268
|
implementation: SwiftUI View wrapping InputTextBase
|
|
264
269
|
file: platforms/ios/InputTextPassword.ios.swift
|
|
265
|
-
toggle: Button with SF Symbol eye/eye.slash
|
|
270
|
+
toggle: Button with SF Symbol eye/eye.slash, injected via the base's trailingContent slot
|
|
266
271
|
autocomplete: UITextContentType.password or .newPassword
|
|
272
|
+
visibility: Toggles by switching base type between .password and .text
|
|
267
273
|
android:
|
|
268
274
|
implementation: Jetpack Compose Composable wrapping InputTextBase
|
|
269
275
|
file: platforms/android/InputTextPassword.android.kt
|
|
270
|
-
toggle: IconButton with visibility icons
|
|
276
|
+
toggle: IconButton with visibility icons, injected via the base's trailingContent slot
|
|
271
277
|
autocomplete: Autofill framework password type
|
|
278
|
+
visibility: Toggles via the base's visualTransformation override (type stays PASSWORD)
|
|
272
279
|
|
|
273
280
|
# Inheritance relationship
|
|
274
281
|
inheritance:
|
|
@@ -178,7 +178,7 @@ InputTextPassword(
|
|
|
178
178
|
| `isSuccess` | boolean | No | false | Success state indicator |
|
|
179
179
|
| `showInfoIcon` | boolean | No | false | Show info icon |
|
|
180
180
|
| `placeholder` | string | No | - | Placeholder text |
|
|
181
|
-
| `readOnly` | boolean | No | false |
|
|
181
|
+
| `readOnly` | boolean | No | false | Accepted but **ignored** — readOnly is contracted out of Input-Text-Password (never forwarded to the base; see contracts.yaml `excludes.state_readonly` and the adjudication `.kiro/issues/input-text-base-ios-readonly-adjudication.md`, RULED B-prime 2026-07-15) |
|
|
182
182
|
| `required` | boolean | No | false | Required field indicator |
|
|
183
183
|
| `maxLength` | number | No | - | Maximum input length |
|
|
184
184
|
| `testID` | string | No | - | Test ID for automation |
|
|
@@ -216,16 +216,17 @@ InputTextPassword(
|
|
|
216
216
|
3. **validates_on_blur**: Validation triggers on blur
|
|
217
217
|
4. **error_state_display**: Shows error message and styling
|
|
218
218
|
5. **success_state_display**: Shows success styling
|
|
219
|
-
6. **
|
|
220
|
-
7. **
|
|
221
|
-
8. **
|
|
222
|
-
|
|
219
|
+
6. **trailing_icon_display**: Shows contextual trailing icons
|
|
220
|
+
7. **focus_ring**: WCAG 2.4.7 focus visible indicator
|
|
221
|
+
8. **reduced_motion_support**: Respects prefers-reduced-motion
|
|
222
|
+
|
|
223
|
+
**Excluded**: `state_disabled` — DesignerPunk does not support disabled states for usability and accessibility reasons. If an action is unavailable, the component should not be rendered (adjudicated 2026-07-15).
|
|
223
224
|
|
|
224
225
|
### Password-Specific Contracts
|
|
225
226
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
227
|
+
9. **provides_secure_input**: Masks password input by default
|
|
228
|
+
10. **supports_password_toggle**: Show/hide password functionality
|
|
229
|
+
11. **provides_password_autocomplete**: Enables browser/platform password autofill
|
|
229
230
|
|
|
230
231
|
## Validation
|
|
231
232
|
|
|
@@ -61,3 +61,8 @@ excludes:
|
|
|
61
61
|
state_disabled:
|
|
62
62
|
reason: "DesignerPunk does not support disabled states for usability and accessibility reasons. If an action is unavailable, the component should not be rendered."
|
|
63
63
|
category: state
|
|
64
|
+
|
|
65
|
+
state_readonly:
|
|
66
|
+
reason: "readOnly is contracted out of Input-Text-Password: zero named consumers, and excluding it deletes a security fork — the base's iOS read-only path (B-prime) renders the value as selectable plaintext Text, which must never be reachable for a secret. The profile idiom for a locked password is a display row ('Password ••••••••') plus a Change action, not a read-only password field. Implementations strip/ignore the inherited readOnly prop. If a product case ever materializes, the invariant-based fallback rule recorded in the adjudication ([DATA R2] §3: masked value never copyable; plaintext never in the accessibility tree while masked; announced semantically as hidden; no keyboard; toggle functional; revealed plaintext selectable/copyable) is the recorded alternative."
|
|
67
|
+
category: state
|
|
68
|
+
reference: ".kiro/issues/input-text-base-ios-readonly-adjudication.md (RULED B-prime, Peter 2026-07-15, condition 4)"
|