@3fn/core 13.0.0 → 14.0.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 +5 -5
- package/.kiro/steering/DesignerPunk-Systems-Overview.md +6 -6
- 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 +14 -3
- package/application-mcp-server/src/index.ts +26 -0
- package/dist/ComponentTokens.android.kt +1 -1
- package/dist/ComponentTokens.ios.swift +1 -1
- package/dist/ComponentTokens.web.css +1 -1
- 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 +22 -81
- package/dist/browser/designerpunk.esm.min.js +29 -32
- package/dist/browser/designerpunk.umd.js +22 -81
- package/dist/browser/designerpunk.umd.min.js +42 -45
- package/dist/browser/tokens.css +1 -1
- 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/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/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/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/Component-Development-Guide.md +22 -12
- package/governance/Component-Development-Standards.md +2 -2
- package/governance/Component-Family-Avatar.md +0 -1
- package/governance/Component-Family-Badge.md +0 -1
- package/governance/Component-Family-Button.md +4 -17
- package/governance/Component-Family-Chip.md +0 -1
- package/governance/Component-Family-Container.md +0 -1
- package/governance/Component-Family-Data-Display.md +1 -2
- package/governance/Component-Family-Divider.md +1 -2
- package/governance/Component-Family-Form-Inputs.md +5 -5
- package/governance/Component-Family-Icon.md +1 -2
- package/governance/Component-Family-Loading.md +1 -2
- package/governance/Component-Family-Modal.md +1 -2
- package/governance/Component-Family-Navigation.md +0 -1
- package/governance/Component-Family-Progress.md +0 -1
- package/governance/Component-Inheritance-Structures.md +195 -76
- 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 +54 -38
- package/governance/Component-Templates.md +32 -36
- package/governance/Contract-System-Reference.md +6 -6
- 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 +24 -24
- package/governance/Process-Hook-Operations.md +19 -7
- package/governance/Process-Orchestration-Model-Selection.md +92 -0
- package/governance/Process-Spec-Planning.md +91 -39
- package/governance/Process-Task-Type-Definitions.md +80 -4
- package/governance/Product-Handoff-Protocol.md +2 -0
- package/governance/Rosetta-System-Architecture.md +6 -6
- 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 +21 -21
- 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 +368 -0
- package/governance/completion-documentation-guide.md +11 -8
- 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 +1 -1
- package/governance/release-management-system.md +2 -2
- 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 +23 -21
- 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/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/contracts.yaml +11 -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/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/__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/semantic/BlendTokens.ts +26 -5
- package/src/tokens/semantic/OpacityTokens.ts +4 -4
- package/src/tools/release/__tests__/ReleasePipeline.test.ts +1 -1
- 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/semantics.yaml +1 -2
|
@@ -13,17 +13,17 @@
|
|
|
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)
|
|
16
17
|
* - Respects reduce motion settings
|
|
17
18
|
* - WCAG 2.1 AA compliant
|
|
18
|
-
* - Uses theme-aware blend utilities for state colors (focus
|
|
19
|
-
*
|
|
19
|
+
* - Uses theme-aware blend utilities for state colors (focus)
|
|
20
|
+
*
|
|
20
21
|
* Behavioral Contracts:
|
|
21
22
|
* - focusable: Can receive keyboard focus
|
|
22
23
|
* - float_label_animation: Label animates on focus
|
|
23
24
|
* - validates_on_blur: Validation triggers on blur
|
|
24
25
|
* - error_state_display: Shows error message and styling
|
|
25
26
|
* - success_state_display: Shows success styling
|
|
26
|
-
* - disabled_state: Prevents interaction when disabled
|
|
27
27
|
* - trailing_icon_display: Shows contextual trailing icons
|
|
28
28
|
* - focus_ring: WCAG 2.4.7 focus visible indicator
|
|
29
29
|
* - reduced_motion_support: Respects prefers-reduced-motion
|
|
@@ -57,6 +57,7 @@ import androidx.compose.ui.platform.LocalDensity
|
|
|
57
57
|
import androidx.compose.ui.semantics.contentDescription
|
|
58
58
|
import androidx.compose.ui.semantics.error
|
|
59
59
|
import androidx.compose.ui.semantics.semantics
|
|
60
|
+
import androidx.compose.ui.semantics.stateDescription
|
|
60
61
|
import androidx.compose.ui.text.TextStyle
|
|
61
62
|
import androidx.compose.ui.text.font.FontWeight
|
|
62
63
|
import androidx.compose.ui.text.input.ImeAction
|
|
@@ -68,7 +69,6 @@ import androidx.compose.ui.unit.dp
|
|
|
68
69
|
import androidx.compose.ui.unit.sp
|
|
69
70
|
import android.provider.Settings
|
|
70
71
|
import com.designerpunk.tokens.focusBlend
|
|
71
|
-
import com.designerpunk.tokens.disabledBlend
|
|
72
72
|
|
|
73
73
|
/**
|
|
74
74
|
* Input type enumeration
|
|
@@ -99,13 +99,17 @@ enum class InputType {
|
|
|
99
99
|
* @param isSuccess Success state indicator
|
|
100
100
|
* @param showInfoIcon Info icon support
|
|
101
101
|
* @param type Input type
|
|
102
|
+
* @param visualTransformation Override for the input's visual transformation.
|
|
103
|
+
* When null, derived from type (PasswordVisualTransformation for PASSWORD).
|
|
104
|
+
* Semantic variants use this to control masking (e.g., password visibility toggle).
|
|
102
105
|
* @param placeholder Placeholder text (only shown when label is floated and input is empty)
|
|
103
106
|
* @param readOnly Read-only state
|
|
104
107
|
* @param required Required field indicator
|
|
105
108
|
* @param maxLength Maximum length for input value
|
|
106
109
|
* @param imeAction IME action for keyboard
|
|
107
110
|
* @param keyboardActions Keyboard actions
|
|
108
|
-
* @param
|
|
111
|
+
* @param trailingContent Trailing content slot rendered after the status icons
|
|
112
|
+
* (e.g., password visibility toggle)
|
|
109
113
|
*/
|
|
110
114
|
@Composable
|
|
111
115
|
fun InputTextBase(
|
|
@@ -121,13 +125,14 @@ fun InputTextBase(
|
|
|
121
125
|
isSuccess: Boolean = false,
|
|
122
126
|
showInfoIcon: Boolean = false,
|
|
123
127
|
type: InputType = InputType.TEXT,
|
|
128
|
+
visualTransformation: VisualTransformation? = null,
|
|
124
129
|
placeholder: String? = null,
|
|
125
130
|
readOnly: Boolean = false,
|
|
126
131
|
required: Boolean = false,
|
|
127
132
|
maxLength: Int? = null,
|
|
128
133
|
imeAction: ImeAction = ImeAction.Done,
|
|
129
134
|
keyboardActions: KeyboardActions = KeyboardActions.Default,
|
|
130
|
-
|
|
135
|
+
trailingContent: (@Composable () -> Unit)? = null
|
|
131
136
|
) {
|
|
132
137
|
// State
|
|
133
138
|
var isFocused by remember { mutableStateOf(false) }
|
|
@@ -192,7 +197,6 @@ fun InputTextBase(
|
|
|
192
197
|
targetValue = when {
|
|
193
198
|
hasError -> colorFeedbackErrorText
|
|
194
199
|
isSuccess -> colorFeedbackSuccessText
|
|
195
|
-
isDisabled -> colorActionPrimary.disabledBlend()
|
|
196
200
|
isFocused -> colorActionPrimary.focusBlend()
|
|
197
201
|
else -> colorTextMuted
|
|
198
202
|
},
|
|
@@ -204,7 +208,6 @@ fun InputTextBase(
|
|
|
204
208
|
targetValue = when {
|
|
205
209
|
hasError -> colorFeedbackErrorText
|
|
206
210
|
isSuccess -> colorFeedbackSuccessText
|
|
207
|
-
isDisabled -> colorActionPrimary.disabledBlend()
|
|
208
211
|
isFocused -> colorActionPrimary.focusBlend()
|
|
209
212
|
else -> colorBorder
|
|
210
213
|
},
|
|
@@ -230,6 +233,14 @@ fun InputTextBase(
|
|
|
230
233
|
if (errorMessage != null) {
|
|
231
234
|
error(errorMessage)
|
|
232
235
|
}
|
|
236
|
+
if (readOnly) {
|
|
237
|
+
// Explicit TalkBack read-only announcement — label, value,
|
|
238
|
+
// and "read only", never a disabled/dimmed state
|
|
239
|
+
// (state_readonly contract, announcement-parity condition 3:
|
|
240
|
+
// .kiro/issues/input-text-base-ios-readonly-adjudication.md,
|
|
241
|
+
// RULED B-prime, Peter 2026-07-15).
|
|
242
|
+
stateDescription = "read only"
|
|
243
|
+
}
|
|
233
244
|
}
|
|
234
245
|
) {
|
|
235
246
|
// Input wrapper with label and trailing icon
|
|
@@ -278,7 +289,7 @@ fun InputTextBase(
|
|
|
278
289
|
shape = RoundedCornerShape(radius150)
|
|
279
290
|
)
|
|
280
291
|
.then(
|
|
281
|
-
if (isFocused
|
|
292
|
+
if (isFocused) {
|
|
282
293
|
Modifier.border(
|
|
283
294
|
width = accessibilityFocusWidth,
|
|
284
295
|
color = accessibilityFocusColor,
|
|
@@ -295,7 +306,7 @@ fun InputTextBase(
|
|
|
295
306
|
lineHeight = typographyInputLineHeight.sp,
|
|
296
307
|
fontWeight = FontWeight(typographyInputFontWeight),
|
|
297
308
|
letterSpacing = typographyInputLetterSpacing.sp,
|
|
298
|
-
color =
|
|
309
|
+
color = colorTextDefault
|
|
299
310
|
),
|
|
300
311
|
keyboardOptions = KeyboardOptions(
|
|
301
312
|
keyboardType = getKeyboardType(type),
|
|
@@ -303,8 +314,8 @@ fun InputTextBase(
|
|
|
303
314
|
),
|
|
304
315
|
keyboardActions = keyboardActions,
|
|
305
316
|
singleLine = true,
|
|
306
|
-
readOnly = readOnly
|
|
307
|
-
visualTransformation = if (type == InputType.PASSWORD) {
|
|
317
|
+
readOnly = readOnly,
|
|
318
|
+
visualTransformation = visualTransformation ?: if (type == InputType.PASSWORD) {
|
|
308
319
|
PasswordVisualTransformation()
|
|
309
320
|
} else {
|
|
310
321
|
VisualTransformation.None
|
|
@@ -373,6 +384,9 @@ fun InputTextBase(
|
|
|
373
384
|
)
|
|
374
385
|
}
|
|
375
386
|
}
|
|
387
|
+
|
|
388
|
+
// Trailing content slot (e.g., password visibility toggle)
|
|
389
|
+
trailingContent?.invoke()
|
|
376
390
|
}
|
|
377
391
|
|
|
378
392
|
// Helper text (persistent)
|
|
@@ -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
|
|