@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
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* composition pattern as iOS and Android platforms. This ensures cross-platform
|
|
13
13
|
* consistency and single source of truth for icon rendering.
|
|
14
14
|
*
|
|
15
|
-
* Uses theme-aware blend utilities for state colors (hover, pressed,
|
|
15
|
+
* Uses theme-aware blend utilities for state colors (hover, pressed, icon)
|
|
16
16
|
* instead of opacity or filter workarounds. This ensures cross-platform consistency
|
|
17
17
|
* with iOS and Android implementations.
|
|
18
18
|
*
|
|
@@ -86,13 +86,12 @@ function getIconSizeForButton(buttonSize: ButtonSize): IconBaseSize {
|
|
|
86
86
|
* - Semantic `<button>` element with proper accessibility
|
|
87
87
|
* - Token-based styling via CSS custom properties
|
|
88
88
|
* - Supports text wrapping by default (accessibility-first)
|
|
89
|
-
* - Uses blend utilities for state colors (hover, pressed,
|
|
89
|
+
* - Uses blend utilities for state colors (hover, pressed, icon)
|
|
90
90
|
* - WCAG 2.1 AA compliant:
|
|
91
91
|
* - Keyboard navigation (Tab, Enter, Space)
|
|
92
92
|
* - Focus indicators with 3:1 contrast ratio
|
|
93
93
|
* - Color contrast 4.5:1 for all styles
|
|
94
94
|
* - Screen reader support with ARIA attributes
|
|
95
|
-
* - Proper disabled state handling
|
|
96
95
|
*
|
|
97
96
|
* @example
|
|
98
97
|
* ```html
|
|
@@ -106,7 +105,6 @@ function getIconSizeForButton(buttonSize: ButtonSize): IconBaseSize {
|
|
|
106
105
|
* variant="primary"
|
|
107
106
|
* icon="arrow-right"
|
|
108
107
|
* no-wrap="false"
|
|
109
|
-
* disabled="false"
|
|
110
108
|
* test-id="submit-button"
|
|
111
109
|
* ></button-cta>
|
|
112
110
|
* ```
|
|
@@ -143,7 +141,6 @@ export class ButtonCTA extends HTMLElement {
|
|
|
143
141
|
// Cached blend colors for state styling
|
|
144
142
|
private _hoverColor: string = '';
|
|
145
143
|
private _pressedColor: string = '';
|
|
146
|
-
private _disabledColor: string = '';
|
|
147
144
|
private _iconColor: string = '';
|
|
148
145
|
private _iconOpticalBalanceColor: string = '';
|
|
149
146
|
|
|
@@ -153,7 +150,7 @@ export class ButtonCTA extends HTMLElement {
|
|
|
153
150
|
* When these attributes change, attributeChangedCallback is invoked.
|
|
154
151
|
*/
|
|
155
152
|
static get observedAttributes(): string[] {
|
|
156
|
-
return ['label', 'size', 'variant', 'icon', 'no-wrap', '
|
|
153
|
+
return ['label', 'size', 'variant', 'icon', 'no-wrap', 'test-id'];
|
|
157
154
|
}
|
|
158
155
|
|
|
159
156
|
constructor() {
|
|
@@ -219,15 +216,14 @@ export class ButtonCTA extends HTMLElement {
|
|
|
219
216
|
* Calculate blend colors from CSS custom properties.
|
|
220
217
|
*
|
|
221
218
|
* Reads base colors from CSS custom properties and applies theme-aware blend
|
|
222
|
-
* utilities to generate state colors (hover, pressed,
|
|
223
|
-
*
|
|
219
|
+
* utilities to generate state colors (hover, pressed, icon).
|
|
220
|
+
*
|
|
224
221
|
* Uses getBlendUtilities() factory functions instead of direct blend calculations
|
|
225
222
|
* for cross-platform consistency with iOS and Android implementations.
|
|
226
|
-
*
|
|
223
|
+
*
|
|
227
224
|
* State color mappings:
|
|
228
225
|
* - Hover: darkerBlend(color.action.primary, blend.hoverDarker) - 8% darker
|
|
229
226
|
* - Pressed: darkerBlend(color.action.primary, blend.pressedDarker) - 12% darker
|
|
230
|
-
* - Disabled: desaturate(color.action.primary, blend.disabledDesaturate) - 12% less saturated
|
|
231
227
|
* - Icon: lighterBlend(color.contrast.onAction, blend.iconLighter) - 8% lighter
|
|
232
228
|
*
|
|
233
229
|
* @see Requirements: 7.1, 7.2, 7.3, 7.4 - Button-CTA state colors
|
|
@@ -258,10 +254,7 @@ export class ButtonCTA extends HTMLElement {
|
|
|
258
254
|
|
|
259
255
|
// @see Requirements: 7.2 - Pressed uses darkerBlend(color.action.primary, blend.pressedDarker)
|
|
260
256
|
this._pressedColor = this._blendUtils.pressedColor(primaryColor);
|
|
261
|
-
|
|
262
|
-
// @see Requirements: 7.3 - Disabled uses desaturate(color.action.primary, blend.disabledDesaturate)
|
|
263
|
-
this._disabledColor = this._blendUtils.disabledColor(primaryColor);
|
|
264
|
-
|
|
257
|
+
|
|
265
258
|
// @see Requirements: 7.4 - Icon uses lighterBlend(color.contrast.onAction, blend.iconLighter)
|
|
266
259
|
// Icon for primary buttons: provides optical balance for icons on primary background
|
|
267
260
|
this._iconColor = this._blendUtils.iconColor(onActionColor);
|
|
@@ -371,24 +364,6 @@ export class ButtonCTA extends HTMLElement {
|
|
|
371
364
|
this.setAttribute('no-wrap', value.toString());
|
|
372
365
|
}
|
|
373
366
|
|
|
374
|
-
/**
|
|
375
|
-
* Get the disabled state.
|
|
376
|
-
*/
|
|
377
|
-
get disabled(): boolean {
|
|
378
|
-
return this.hasAttribute('disabled');
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
/**
|
|
382
|
-
* Set the disabled state.
|
|
383
|
-
*/
|
|
384
|
-
set disabled(value: boolean) {
|
|
385
|
-
if (value) {
|
|
386
|
-
this.setAttribute('disabled', '');
|
|
387
|
-
} else {
|
|
388
|
-
this.removeAttribute('disabled');
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
|
|
392
367
|
/**
|
|
393
368
|
* Get the test ID.
|
|
394
369
|
*/
|
|
@@ -451,17 +426,15 @@ export class ButtonCTA extends HTMLElement {
|
|
|
451
426
|
const variant = this.buttonVariant;
|
|
452
427
|
const icon = this.icon;
|
|
453
428
|
const noWrap = this.noWrap;
|
|
454
|
-
const disabled = this.disabled;
|
|
455
429
|
const testID = this.testID;
|
|
456
|
-
|
|
430
|
+
|
|
457
431
|
// Generate class names
|
|
458
432
|
const buttonClasses = [
|
|
459
433
|
'button-cta',
|
|
460
434
|
`button-cta--${size}`,
|
|
461
|
-
`button-cta--${variant}
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
435
|
+
`button-cta--${variant}`
|
|
436
|
+
].join(' ');
|
|
437
|
+
|
|
465
438
|
// Get icon size for button size variant
|
|
466
439
|
// Icon sizes are type-safe mapped from button size:
|
|
467
440
|
// - Small/Medium: 24px (iconBaseSize100)
|
|
@@ -480,7 +453,6 @@ export class ButtonCTA extends HTMLElement {
|
|
|
480
453
|
const blendColorStyles = `
|
|
481
454
|
--_cta-hover-bg: ${this._hoverColor};
|
|
482
455
|
--_cta-pressed-bg: ${this._pressedColor};
|
|
483
|
-
--_cta-disabled-bg: ${this._disabledColor};
|
|
484
456
|
--_cta-icon-color: ${this._iconColor};
|
|
485
457
|
--_cta-icon-optical: ${this._iconOpticalBalanceColor};
|
|
486
458
|
`;
|
|
@@ -491,11 +463,10 @@ export class ButtonCTA extends HTMLElement {
|
|
|
491
463
|
// @see Requirements: 8.2, 8.3 (components render correctly with interactivity)
|
|
492
464
|
this._shadowRoot.innerHTML = `
|
|
493
465
|
<style>${buttonStyles}</style>
|
|
494
|
-
<button
|
|
466
|
+
<button
|
|
495
467
|
class="${buttonClasses}"
|
|
496
468
|
type="button"
|
|
497
469
|
role="button"
|
|
498
|
-
${disabled ? 'disabled aria-disabled="true"' : 'aria-disabled="false"'}
|
|
499
470
|
${testIDAttr}
|
|
500
471
|
aria-label="${label}"
|
|
501
472
|
style="${blendColorStyles}"
|
|
@@ -536,34 +507,23 @@ export class ButtonCTA extends HTMLElement {
|
|
|
536
507
|
const variant = this.buttonVariant;
|
|
537
508
|
const icon = this.icon;
|
|
538
509
|
const noWrap = this.noWrap;
|
|
539
|
-
const disabled = this.disabled;
|
|
540
510
|
const testID = this.testID;
|
|
541
|
-
|
|
511
|
+
|
|
542
512
|
// Get icon size for button size variant
|
|
543
513
|
const iconSize: IconBaseSize = getIconSizeForButton(size);
|
|
544
|
-
|
|
514
|
+
|
|
545
515
|
// ─────────────────────────────────────────────────────────────────
|
|
546
516
|
// Update button element
|
|
547
517
|
// ─────────────────────────────────────────────────────────────────
|
|
548
|
-
|
|
518
|
+
|
|
549
519
|
// Update CSS class (preserves element identity for transitions)
|
|
550
520
|
const buttonClasses = [
|
|
551
521
|
'button-cta',
|
|
552
522
|
`button-cta--${size}`,
|
|
553
|
-
`button-cta--${variant}
|
|
554
|
-
|
|
555
|
-
].filter(Boolean).join(' ');
|
|
523
|
+
`button-cta--${variant}`
|
|
524
|
+
].join(' ');
|
|
556
525
|
this._button.className = buttonClasses;
|
|
557
|
-
|
|
558
|
-
// Update disabled state
|
|
559
|
-
if (disabled) {
|
|
560
|
-
this._button.setAttribute('disabled', '');
|
|
561
|
-
this._button.setAttribute('aria-disabled', 'true');
|
|
562
|
-
} else {
|
|
563
|
-
this._button.removeAttribute('disabled');
|
|
564
|
-
this._button.setAttribute('aria-disabled', 'false');
|
|
565
|
-
}
|
|
566
|
-
|
|
526
|
+
|
|
567
527
|
// Update aria-label
|
|
568
528
|
this._button.setAttribute('aria-label', label);
|
|
569
529
|
|
|
@@ -577,7 +537,6 @@ export class ButtonCTA extends HTMLElement {
|
|
|
577
537
|
// Update blend color CSS custom properties
|
|
578
538
|
this._button.style.setProperty('--_cta-hover-bg', this._hoverColor);
|
|
579
539
|
this._button.style.setProperty('--_cta-pressed-bg', this._pressedColor);
|
|
580
|
-
this._button.style.setProperty('--_cta-disabled-bg', this._disabledColor);
|
|
581
540
|
this._button.style.setProperty('--_cta-icon-color', this._iconColor);
|
|
582
541
|
this._button.style.setProperty('--_cta-icon-optical', this._iconOpticalBalanceColor);
|
|
583
542
|
|
|
@@ -638,13 +597,6 @@ export class ButtonCTA extends HTMLElement {
|
|
|
638
597
|
* Dispatches a custom 'press' event that bubbles up to parent elements.
|
|
639
598
|
*/
|
|
640
599
|
private _handleClick = (event: Event): void => {
|
|
641
|
-
// Don't dispatch if disabled
|
|
642
|
-
if (this.disabled) {
|
|
643
|
-
event.preventDefault();
|
|
644
|
-
event.stopPropagation();
|
|
645
|
-
return;
|
|
646
|
-
}
|
|
647
|
-
|
|
648
600
|
// Dispatch custom 'press' event
|
|
649
601
|
this.dispatchEvent(new CustomEvent('press', {
|
|
650
602
|
bubbles: true,
|
|
@@ -663,11 +615,6 @@ export class ButtonCTA extends HTMLElement {
|
|
|
663
615
|
* @param event - The keyboard event
|
|
664
616
|
*/
|
|
665
617
|
private _handleKeyDown = (event: KeyboardEvent): void => {
|
|
666
|
-
// Don't handle if disabled
|
|
667
|
-
if (this.disabled) {
|
|
668
|
-
return;
|
|
669
|
-
}
|
|
670
|
-
|
|
671
618
|
// Handle Enter and Space keys (WCAG 2.1 AA keyboard navigation)
|
|
672
619
|
if (event.key === 'Enter' || event.key === ' ') {
|
|
673
620
|
event.preventDefault(); // Prevent default space scrolling
|
|
@@ -82,7 +82,6 @@ export type ButtonStyle = 'primary' | 'secondary' | 'tertiary';
|
|
|
82
82
|
* variant="primary"
|
|
83
83
|
* icon="arrow-right"
|
|
84
84
|
* noWrap={false}
|
|
85
|
-
* disabled={false}
|
|
86
85
|
* testID="submit-button"
|
|
87
86
|
* onPress={handleSubmit}
|
|
88
87
|
* />
|
|
@@ -197,15 +196,13 @@ export interface ButtonProps {
|
|
|
197
196
|
|
|
198
197
|
/**
|
|
199
198
|
* Click/tap handler (required)
|
|
200
|
-
*
|
|
199
|
+
*
|
|
201
200
|
* Callback function invoked when the button is clicked or tapped.
|
|
202
|
-
*
|
|
203
|
-
*
|
|
201
|
+
*
|
|
204
202
|
* @remarks
|
|
205
203
|
* - Function signature: `() => void`
|
|
206
|
-
* - Not invoked when `disabled` is true
|
|
207
204
|
* - Triggered by click, tap, Enter key, or Space key
|
|
208
|
-
*
|
|
205
|
+
*
|
|
209
206
|
* @example
|
|
210
207
|
* ```typescript
|
|
211
208
|
* onPress={() => console.log('Clicked')}
|
|
@@ -232,26 +229,11 @@ export interface ButtonProps {
|
|
|
232
229
|
* ```
|
|
233
230
|
*/
|
|
234
231
|
testID?: string;
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* Optional disabled state (default: false)
|
|
238
|
-
*
|
|
239
|
-
* When true, the button is non-interactive and visually indicates
|
|
240
|
-
* disabled state. The onPress handler is not called when disabled.
|
|
241
|
-
*
|
|
242
|
-
* @defaultValue false
|
|
243
|
-
*
|
|
244
|
-
* @remarks
|
|
245
|
-
* - Disabled buttons are not keyboard focusable
|
|
246
|
-
* - Screen readers announce disabled state
|
|
247
|
-
* - Visual styling indicates non-interactive state
|
|
248
|
-
*
|
|
249
|
-
* @example
|
|
250
|
-
* ```typescript
|
|
251
|
-
* disabled={false} // Interactive (default)
|
|
252
|
-
* disabled={true} // Non-interactive
|
|
253
|
-
* disabled={isSubmitting} // Conditional disable
|
|
254
|
-
* ```
|
|
255
|
-
*/
|
|
256
|
-
disabled?: boolean;
|
|
257
232
|
}
|
|
233
|
+
|
|
234
|
+
// NO DISABLED STATES
|
|
235
|
+
// DesignerPunk does not support disabled states for usability and
|
|
236
|
+
// accessibility reasons. If an action is unavailable, the component
|
|
237
|
+
// should not be rendered. For in-flight async actions, use the loading
|
|
238
|
+
// state (state_loading) instead.
|
|
239
|
+
// Ruled by Peter 2026-07-15 (Button-CTA disabled-state adjudication).
|
|
@@ -40,11 +40,24 @@ describe('Chip-Base Web Component', () => {
|
|
|
40
40
|
beforeEach(async () => {
|
|
41
41
|
// Wait for custom element to be defined
|
|
42
42
|
await customElements.whenDefined('chip-base');
|
|
43
|
+
// Silence ONLY the known environmental warn ("Could not calculate blend colors",
|
|
44
|
+
// fired async via rAF): jsdom never applies real CSS, so the token lookup cannot
|
|
45
|
+
// succeed here and the component's designed fallback fires every run, forever.
|
|
46
|
+
// Anything ELSE passes through to the real console — a NEW warn class stays audible.
|
|
47
|
+
const realWarn = console.warn.bind(console);
|
|
48
|
+
jest.spyOn(console, 'warn').mockImplementation((...args: unknown[]) => {
|
|
49
|
+
// Component-prefixed match (Lina review, PR #39): the bare substring is shared by
|
|
50
|
+
// Button components too — prefix-matching prevents accidentally swallowing a
|
|
51
|
+
// cross-component warn if this suite ever composes a Button.
|
|
52
|
+
if (typeof args[0] === 'string' && args[0].includes('ChipBase: Could not calculate blend colors')) return;
|
|
53
|
+
realWarn(...args);
|
|
54
|
+
});
|
|
43
55
|
});
|
|
44
56
|
|
|
45
57
|
afterEach(() => {
|
|
46
58
|
// Clean up any created elements
|
|
47
59
|
cleanupDOM();
|
|
60
|
+
jest.restoreAllMocks();
|
|
48
61
|
});
|
|
49
62
|
|
|
50
63
|
// ============================================================================
|
|
@@ -42,11 +42,24 @@ describe('Chip-Filter Web Component', () => {
|
|
|
42
42
|
beforeEach(async () => {
|
|
43
43
|
// Wait for custom element to be defined
|
|
44
44
|
await customElements.whenDefined('chip-filter');
|
|
45
|
+
// Silence ONLY the known environmental warn ("Could not calculate blend colors",
|
|
46
|
+
// fired async via rAF): jsdom never applies real CSS, so the token lookup cannot
|
|
47
|
+
// succeed here and the component's designed fallback fires every run, forever.
|
|
48
|
+
// Anything ELSE passes through to the real console — a NEW warn class stays audible.
|
|
49
|
+
const realWarn = console.warn.bind(console);
|
|
50
|
+
jest.spyOn(console, 'warn').mockImplementation((...args: unknown[]) => {
|
|
51
|
+
// Component-prefixed match (Lina review, PR #39): the bare substring is shared by
|
|
52
|
+
// Button components too — prefix-matching prevents accidentally swallowing a
|
|
53
|
+
// cross-component warn if this suite ever composes a Button.
|
|
54
|
+
if (typeof args[0] === 'string' && args[0].includes('ChipFilter: Could not calculate blend colors')) return;
|
|
55
|
+
realWarn(...args);
|
|
56
|
+
});
|
|
45
57
|
});
|
|
46
58
|
|
|
47
59
|
afterEach(() => {
|
|
48
60
|
// Clean up any created elements
|
|
49
61
|
cleanupDOM();
|
|
62
|
+
jest.restoreAllMocks();
|
|
50
63
|
});
|
|
51
64
|
|
|
52
65
|
// ============================================================================
|
|
@@ -41,11 +41,24 @@ describe('Chip-Input Web Component', () => {
|
|
|
41
41
|
beforeEach(async () => {
|
|
42
42
|
// Wait for custom element to be defined
|
|
43
43
|
await customElements.whenDefined('chip-input');
|
|
44
|
+
// Silence ONLY the known environmental warn ("Could not calculate blend colors",
|
|
45
|
+
// fired async via rAF): jsdom never applies real CSS, so the token lookup cannot
|
|
46
|
+
// succeed here and the component's designed fallback fires every run, forever.
|
|
47
|
+
// Anything ELSE passes through to the real console — a NEW warn class stays audible.
|
|
48
|
+
const realWarn = console.warn.bind(console);
|
|
49
|
+
jest.spyOn(console, 'warn').mockImplementation((...args: unknown[]) => {
|
|
50
|
+
// Component-prefixed match (Lina review, PR #39): the bare substring is shared by
|
|
51
|
+
// Button components too — prefix-matching prevents accidentally swallowing a
|
|
52
|
+
// cross-component warn if this suite ever composes a Button.
|
|
53
|
+
if (typeof args[0] === 'string' && args[0].includes('ChipInput: Could not calculate blend colors')) return;
|
|
54
|
+
realWarn(...args);
|
|
55
|
+
});
|
|
44
56
|
});
|
|
45
57
|
|
|
46
58
|
afterEach(() => {
|
|
47
59
|
// Clean up any created elements
|
|
48
60
|
cleanupDOM();
|
|
61
|
+
jest.restoreAllMocks();
|
|
49
62
|
});
|
|
50
63
|
|
|
51
64
|
// ============================================================================
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
name: Input-Text-Base
|
|
12
12
|
type: primitive
|
|
13
13
|
family: FormInput
|
|
14
|
-
version: "1.
|
|
14
|
+
version: "1.1.0"
|
|
15
15
|
readiness:
|
|
16
16
|
web:
|
|
17
17
|
reviewed: true
|
|
@@ -25,6 +25,16 @@ composition:
|
|
|
25
25
|
- component: Icon-Base
|
|
26
26
|
role: Trailing contextual icon (error, success, info)
|
|
27
27
|
required: false
|
|
28
|
+
slots:
|
|
29
|
+
- name: trailingContent
|
|
30
|
+
description: >-
|
|
31
|
+
Trailing content slot rendered after the status icons. Semantic
|
|
32
|
+
variants use it to inject interactive trailing controls (e.g.,
|
|
33
|
+
Input-Text-Password's visibility toggle). Native platforms only
|
|
34
|
+
(iOS: AnyView?, Android: @Composable lambda) — the web base has no
|
|
35
|
+
equivalent slot; web variants render trailing controls in their
|
|
36
|
+
own Shadow DOM.
|
|
37
|
+
required: false
|
|
28
38
|
|
|
29
39
|
# Component description
|
|
30
40
|
description: |
|
|
@@ -113,7 +123,11 @@ properties:
|
|
|
113
123
|
type: boolean
|
|
114
124
|
required: false
|
|
115
125
|
default: false
|
|
116
|
-
description:
|
|
126
|
+
description: >-
|
|
127
|
+
Read-only display state (state_readonly contract) — the value is
|
|
128
|
+
non-editable but stays perceivable, selectable, and copyable; no
|
|
129
|
+
keyboard/IME is raised and disabled semantics are never applied. iOS
|
|
130
|
+
renders it as selectable static text inside the field chrome.
|
|
117
131
|
|
|
118
132
|
required:
|
|
119
133
|
type: boolean
|
|
@@ -188,9 +202,23 @@ platform_notes:
|
|
|
188
202
|
ios:
|
|
189
203
|
implementation: SwiftUI View
|
|
190
204
|
file: platforms/ios/InputTextBase.ios.swift
|
|
205
|
+
notes: |
|
|
206
|
+
Platform composition parameters (not part of the cross-platform props):
|
|
207
|
+
- trailingContent: AnyView? = nil — trailing content slot (see composition.slots)
|
|
208
|
+
Masking: type .password renders SecureField; other types render TextField.
|
|
209
|
+
Variants toggle visibility by switching type between .password and .text.
|
|
191
210
|
android:
|
|
192
211
|
implementation: Jetpack Compose Composable
|
|
193
212
|
file: platforms/android/InputTextBase.android.kt
|
|
213
|
+
notes: |
|
|
214
|
+
Platform composition parameters (not part of the cross-platform props):
|
|
215
|
+
- modifier, imeAction, keyboardActions — standard Compose passthroughs
|
|
216
|
+
- visualTransformation: VisualTransformation? = null — masking override;
|
|
217
|
+
null derives from type (PasswordVisualTransformation for PASSWORD)
|
|
218
|
+
- trailingContent: (@Composable () -> Unit)? = null — trailing content
|
|
219
|
+
slot (see composition.slots)
|
|
220
|
+
Variants toggle password visibility via visualTransformation while
|
|
221
|
+
keeping type = PASSWORD (preserves the password keyboard type).
|
|
194
222
|
|
|
195
223
|
# Semantic components that inherit from this primitive
|
|
196
224
|
semantic_variants:
|
|
@@ -36,11 +36,12 @@ This component guarantees the following behaviors across all platforms:
|
|
|
36
36
|
| `validates_on_blur` | Validation triggers on blur | 3.3.1 |
|
|
37
37
|
| `error_state_display` | Shows error message and styling | 3.3.1, 1.4.1 |
|
|
38
38
|
| `success_state_display` | Shows success styling | 1.4.1 |
|
|
39
|
-
| `disabled_state` | Prevents interaction when disabled | 4.1.2 |
|
|
40
39
|
| `trailing_icon_display` | Shows contextual trailing icons | 1.4.1 |
|
|
41
40
|
| `focus_ring` | WCAG 2.4.7 focus visible indicator | 2.4.7 |
|
|
42
41
|
| `reduced_motion_support` | Respects prefers-reduced-motion | 2.3.3 |
|
|
43
42
|
|
|
43
|
+
**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).
|
|
44
|
+
|
|
44
45
|
---
|
|
45
46
|
|
|
46
47
|
## Related Documentation
|
|
@@ -145,12 +146,27 @@ InputTextBase(
|
|
|
145
146
|
| `type` | `'text' \| 'email' \| 'password' \| 'tel' \| 'url'` | No | `'text'` | Input type |
|
|
146
147
|
| `autocomplete` | `string` | No | - | Autocomplete attribute for browser autofill |
|
|
147
148
|
| `placeholder` | `string` | No | - | Placeholder text (only shown when label is floated and input is empty) |
|
|
148
|
-
| `readOnly` | `boolean` | No | `false` | Read-only state (
|
|
149
|
+
| `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. Ignored for secure types; not supported by Input-Text-Password |
|
|
149
150
|
| `required` | `boolean` | No | `false` | Required field indicator |
|
|
150
151
|
| `maxLength` | `number` | No | - | Maximum length for input value |
|
|
151
152
|
| `testID` | `string` | No | - | Test ID for automated testing |
|
|
152
153
|
| `className` | `string` | No | - | Additional CSS class names (web only) |
|
|
153
154
|
|
|
155
|
+
### Platform Composition Parameters (native only)
|
|
156
|
+
|
|
157
|
+
These parameters exist on the native implementations for platform composition —
|
|
158
|
+
they are not part of the cross-platform props contract above:
|
|
159
|
+
|
|
160
|
+
| Parameter | Platform | Type | Default | Description |
|
|
161
|
+
|-----------|----------|------|---------|-------------|
|
|
162
|
+
| `trailingContent` | iOS | `AnyView?` | `nil` | Trailing content slot rendered after the status icons (e.g., Input-Text-Password's visibility toggle) |
|
|
163
|
+
| `trailingContent` | Android | `(@Composable () -> Unit)?` | `null` | Same slot, Compose form |
|
|
164
|
+
| `visualTransformation` | Android | `VisualTransformation?` | `null` | Masking override; `null` derives from `type` (`PasswordVisualTransformation` for `PASSWORD`). Lets variants toggle password visibility while keeping the password keyboard type |
|
|
165
|
+
| `modifier`, `imeAction`, `keyboardActions` | Android | — | — | Standard Compose passthroughs |
|
|
166
|
+
|
|
167
|
+
The web base has no trailing slot equivalent — web semantic variants render
|
|
168
|
+
trailing controls inside their own Shadow DOM.
|
|
169
|
+
|
|
154
170
|
---
|
|
155
171
|
|
|
156
172
|
## Token Consumption
|
|
@@ -221,6 +237,13 @@ Legacy type aliases are provided for backward compatibility during migration.
|
|
|
221
237
|
|
|
222
238
|
## Changelog
|
|
223
239
|
|
|
240
|
+
### v1.1.0 (July 2026)
|
|
241
|
+
|
|
242
|
+
- Native bases gain a `trailingContent` composition slot (iOS `AnyView?`, Android `@Composable` lambda) so semantic variants can inject trailing controls — required by Input-Text-Password's visibility toggle
|
|
243
|
+
- Android base gains a `visualTransformation` override (null = derived from `type`) so variants can toggle masking without changing the input type
|
|
244
|
+
- Fixed Input-Text-PhoneNumber native callers referencing nonexistent enum members (`.phone`/`PHONE` → `.tel`/`TEL`)
|
|
245
|
+
- Added static base-call alignment test (`src/__tests__/stemma-system/input-text-native-base-call-alignment.test.ts`) guarding native call sites against parameter/enum drift
|
|
246
|
+
|
|
224
247
|
### v1.0.0 (January 2026)
|
|
225
248
|
|
|
226
249
|
- Initial release as Input-Text-Base (renamed from TextInputField)
|
|
@@ -165,8 +165,8 @@ describe('Input-Text-Base Focus Indicators', () => {
|
|
|
165
165
|
expect(iosComponentContent).toContain('accessibilityFocusWidth');
|
|
166
166
|
expect(iosComponentContent).toContain('accessibilityFocusOffset');
|
|
167
167
|
|
|
168
|
-
// Verify focus ring is visible when focused
|
|
169
|
-
expect(iosComponentContent).toContain('.opacity(isFocused
|
|
168
|
+
// Verify focus ring is visible when focused
|
|
169
|
+
expect(iosComponentContent).toContain('.opacity(isFocused ? 1 : 0)');
|
|
170
170
|
});
|
|
171
171
|
|
|
172
172
|
it('should have focus ring animation that respects reduce motion', () => {
|
|
@@ -194,8 +194,8 @@ describe('Input-Text-Base Focus Indicators', () => {
|
|
|
194
194
|
expect(androidComponentContent).toContain('accessibilityFocusColor');
|
|
195
195
|
expect(androidComponentContent).toContain('accessibilityFocusOffset');
|
|
196
196
|
|
|
197
|
-
// Verify focus ring is conditional on focus state
|
|
198
|
-
expect(androidComponentContent).toContain('if (isFocused
|
|
197
|
+
// Verify focus ring is conditional on focus state
|
|
198
|
+
expect(androidComponentContent).toContain('if (isFocused)');
|
|
199
199
|
});
|
|
200
200
|
});
|
|
201
201
|
|
|
@@ -219,16 +219,17 @@ describe('Input-Text-Base Focus Indicators', () => {
|
|
|
219
219
|
|
|
220
220
|
// iOS: Verify focus ring is not conditional on error/success state
|
|
221
221
|
// (it should always show when focused, regardless of validation state)
|
|
222
|
-
//
|
|
223
|
-
//
|
|
222
|
+
// Find the focus ring section in InputTextBaseFieldChrome (the shared
|
|
223
|
+
// field chrome, extracted from InputTextBaseFieldStyle per the B-prime
|
|
224
|
+
// readOnly ruling, 2026-07-15)
|
|
224
225
|
const iosFocusRingSection = iosContent.substring(
|
|
225
|
-
iosContent.indexOf('.opacity(isFocused
|
|
226
|
-
iosContent.indexOf('.opacity(isFocused
|
|
226
|
+
iosContent.indexOf('.opacity(isFocused ? 1 : 0)'),
|
|
227
|
+
iosContent.indexOf('.opacity(isFocused ? 1 : 0)') + 200
|
|
227
228
|
);
|
|
228
|
-
expect(iosFocusRingSection).toContain('.opacity(isFocused
|
|
229
|
-
|
|
230
|
-
// Android: Verify focus ring is conditional on focus state
|
|
231
|
-
expect(androidContent).toContain('if (isFocused
|
|
229
|
+
expect(iosFocusRingSection).toContain('.opacity(isFocused ? 1 : 0)');
|
|
230
|
+
|
|
231
|
+
// Android: Verify focus ring is conditional on focus state
|
|
232
|
+
expect(androidContent).toContain('if (isFocused)');
|
|
232
233
|
});
|
|
233
234
|
});
|
|
234
235
|
|
|
@@ -249,10 +250,10 @@ describe('Input-Text-Base Focus Indicators', () => {
|
|
|
249
250
|
expect(cssContent).toContain(':focus-visible');
|
|
250
251
|
|
|
251
252
|
// iOS: Uses overlay with opacity based on focus state
|
|
252
|
-
expect(iosContent).toContain('.opacity(isFocused
|
|
253
|
-
|
|
253
|
+
expect(iosContent).toContain('.opacity(isFocused ? 1 : 0)');
|
|
254
|
+
|
|
254
255
|
// Android: Uses conditional border based on focus state
|
|
255
|
-
expect(androidContent).toContain('if (isFocused
|
|
256
|
+
expect(androidContent).toContain('if (isFocused)');
|
|
256
257
|
});
|
|
257
258
|
|
|
258
259
|
it('should document WCAG 2.4.7 compliance in behavioral contracts', () => {
|
|
@@ -9,12 +9,29 @@ contracts:
|
|
|
9
9
|
behavior: |
|
|
10
10
|
Component can receive focus via Tab key navigation.
|
|
11
11
|
Focus state is visually indicated and triggers label animation.
|
|
12
|
+
|
|
13
|
+
Platform note — iOS readOnly carve-out (declared, adjudicated): when
|
|
14
|
+
readOnly is true, iOS renders the value as selectable static text
|
|
15
|
+
(see state_readonly) and does NOT participate in keyboard/Full
|
|
16
|
+
Keyboard Access focus order; onFocus/onBlur are declared not-fired on
|
|
17
|
+
that path. Web and Android readOnly fields affirmatively REMAIN in
|
|
18
|
+
focus order and fire focus events (removing an Android readOnly field
|
|
19
|
+
from focus order would require enabled=false — banned disabled
|
|
20
|
+
semantics — so normalizing web/Android down to iOS is not an
|
|
21
|
+
available move). The carve-out is a mitigable declared exception, not
|
|
22
|
+
a settled permanent difference: an on-device .focusable(interactions:)
|
|
23
|
+
prototype is tracked, and if it verifies (FKA reachability + keyboard
|
|
24
|
+
copy), iOS re-enters focus order and this note tightens to full
|
|
25
|
+
three-platform participation.
|
|
26
|
+
Ruling: .kiro/issues/input-text-base-ios-readonly-adjudication.md
|
|
27
|
+
(RULED B-prime, Peter 2026-07-15).
|
|
12
28
|
wcag: "2.1.1 Keyboard"
|
|
13
29
|
platforms: [web, ios, android]
|
|
14
30
|
validation:
|
|
15
31
|
- Tab key moves focus to input
|
|
16
32
|
- Focus state is visually distinct
|
|
17
33
|
- Focus triggers label float animation
|
|
34
|
+
- readOnly fields remain in keyboard focus order on web and Android
|
|
18
35
|
test_approach: |
|
|
19
36
|
- Press Tab, verify input receives focus
|
|
20
37
|
- Verify label animates on focus
|
|
@@ -74,6 +91,79 @@ contracts:
|
|
|
74
91
|
- Set error state, verify border/label/icon/message
|
|
75
92
|
required: true
|
|
76
93
|
|
|
94
|
+
# state_readonly concept ratified into the Concept Catalog via ballot
|
|
95
|
+
# (.kiro/docs/ballots/2026-07-15-state-readonly-concept.md, RATIFIED Peter
|
|
96
|
+
# 2026-07-15). Authored under the B-prime ruling:
|
|
97
|
+
# .kiro/issues/input-text-base-ios-readonly-adjudication.md (RULED by Peter,
|
|
98
|
+
# 2026-07-15).
|
|
99
|
+
state_readonly:
|
|
100
|
+
category: state
|
|
101
|
+
description: Displays a non-editable value that stays perceivable, selectable, and copyable — never disabled semantics
|
|
102
|
+
behavior: |
|
|
103
|
+
When readOnly is true, the component displays its value without
|
|
104
|
+
allowing edits. Invariants on all platforms:
|
|
105
|
+
- Content is selectable and copyable (whole-string copy is the
|
|
106
|
+
accepted iOS behavior — long-press select/copy on static text).
|
|
107
|
+
- Content is not editable; no software keyboard/IME is raised.
|
|
108
|
+
- Assistive technology announces the label, the value, and a
|
|
109
|
+
read-only indication — NEVER a disabled/dimmed trait or state.
|
|
110
|
+
- No disabled semantics of any kind (no SwiftUI .disabled(), no
|
|
111
|
+
Compose enabled=false, no aria-disabled/disabled attribute).
|
|
112
|
+
|
|
113
|
+
Announcement per platform:
|
|
114
|
+
- Web: native readonly attribute semantics (implicit aria-readonly).
|
|
115
|
+
- Android: explicit stateDescription("read only") in semantics.
|
|
116
|
+
- iOS: composed accessibility — label + value + read-only hint.
|
|
117
|
+
|
|
118
|
+
Focus order per platform:
|
|
119
|
+
- Web and Android: readOnly fields affirmatively PARTICIPATE in
|
|
120
|
+
keyboard focus order and fire onFocus/onBlur.
|
|
121
|
+
- iOS: readOnly content is rendered as selectable static text inside
|
|
122
|
+
the shared field chrome (B-prime); it is NOT in keyboard/FKA focus
|
|
123
|
+
order and onFocus/onBlur are DECLARED not-fired — a mitigable
|
|
124
|
+
declared exception. A .focusable(interactions:) on-device prototype
|
|
125
|
+
is tracked; if it verifies, this contract auto-tightens and iOS
|
|
126
|
+
re-enters focus order.
|
|
127
|
+
|
|
128
|
+
Tap-acknowledgment delta (platform note): tapping an Android readOnly
|
|
129
|
+
field focuses it (focus ring engages; empty-field label floats);
|
|
130
|
+
tapping the iOS readOnly text gives no acknowledgment until
|
|
131
|
+
long-press (select/copy). Declared, cosmetic.
|
|
132
|
+
|
|
133
|
+
Empty-value behavior: the label rests in placeholder position on all
|
|
134
|
+
platforms (no platform force-floats it), and the field chrome
|
|
135
|
+
reserves its full height unconditionally.
|
|
136
|
+
|
|
137
|
+
Secure-type carve-out (declared): when Input-Text-Base is used
|
|
138
|
+
directly with a secure/password type, readOnly is IGNORED — the field
|
|
139
|
+
remains an editable secure field. The read-only display path renders
|
|
140
|
+
the value as selectable plaintext and must be unreachable for
|
|
141
|
+
secrets; read-only passwords are excluded at the variant level
|
|
142
|
+
(Input-Text-Password excludes state_readonly and never forwards
|
|
143
|
+
readOnly).
|
|
144
|
+
wcag: "4.1.2 Name, Role, Value"
|
|
145
|
+
platforms: [web, ios, android]
|
|
146
|
+
validation:
|
|
147
|
+
- Value is not editable when readOnly is true
|
|
148
|
+
- Content is selectable and copyable on all platforms
|
|
149
|
+
- No software keyboard/IME is raised
|
|
150
|
+
- Web renders the native readonly attribute
|
|
151
|
+
- Android passes readOnly to the text field with enabled=true and sets
|
|
152
|
+
stateDescription("read only")
|
|
153
|
+
- iOS renders selectable Text (textSelection enabled) inside the
|
|
154
|
+
shared field chrome and composes a read-only accessibility hint
|
|
155
|
+
- No platform applies disabled semantics (.disabled(), enabled=false,
|
|
156
|
+
aria-disabled, dimmed trait)
|
|
157
|
+
- readOnly fields remain in keyboard focus order on web and Android
|
|
158
|
+
test_approach: |
|
|
159
|
+
- Static (Jest): web readonly attribute wiring; Android
|
|
160
|
+
readOnly = readOnly pass-through; iOS textSelection path present and
|
|
161
|
+
no .disabled( anywhere in Form Inputs iOS sources
|
|
162
|
+
- Device: attempt edit (rejected), copy value, verify announcement
|
|
163
|
+
(VoiceOver: label + value + read-only hint, never "dimmed";
|
|
164
|
+
TalkBack: "read only")
|
|
165
|
+
required: true
|
|
166
|
+
|
|
77
167
|
state_success:
|
|
78
168
|
category: state
|
|
79
169
|
description: Shows success styling
|