@sarjallab09/figma-intelligence 1.1.0 → 1.2.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/README.md +67 -36
- package/dist/bin/cli.js +2 -0
- package/dist/design-bridge/bridge.js +2 -0
- package/dist/figma-bridge-plugin/bridge-relay.js +2 -0
- package/dist/figma-bridge-plugin/code.js +1 -0
- package/{figma-bridge-plugin → dist/figma-bridge-plugin}/package-lock.json +0 -3
- package/dist/figma-bridge-plugin/ui.html +4970 -0
- package/dist/figma-intelligence-layer/dist/index.js +2 -0
- package/dist/scripts/clean-existing-chunks.js +2 -0
- package/dist/scripts/connect-ai-tool.js +2 -0
- package/dist/scripts/convert-hub-pdfs.js +2 -0
- package/dist/scripts/figma-mcp-status.js +2 -0
- package/dist/scripts/register-codex-mcp.js +2 -0
- package/dist/scripts/test-copilot-chat.js +2 -0
- package/package.json +11 -8
- package/bin/cli.js +0 -859
- package/design-bridge/bridge.js +0 -196
- package/design-bridge/lib/assets.js +0 -367
- package/design-bridge/lib/prompt.js +0 -85
- package/design-bridge/lib/server.js +0 -66
- package/design-bridge/lib/stitch.js +0 -37
- package/design-bridge/lib/tokens.js +0 -82
- package/design-bridge/package-lock.json +0 -579
- package/figma-bridge-plugin/README.md +0 -97
- package/figma-bridge-plugin/anthropic-chat-runner.js +0 -192
- package/figma-bridge-plugin/bridge-relay.js +0 -2505
- package/figma-bridge-plugin/chat-runner.js +0 -485
- package/figma-bridge-plugin/code.js +0 -1534
- package/figma-bridge-plugin/codex-runner.js +0 -505
- package/figma-bridge-plugin/component-schemas.js +0 -110
- package/figma-bridge-plugin/content-context.js +0 -869
- package/figma-bridge-plugin/create-button.js +0 -216
- package/figma-bridge-plugin/gemini-cli-runner.js +0 -291
- package/figma-bridge-plugin/gemini-runner.js +0 -187
- package/figma-bridge-plugin/html-to-figma.js +0 -927
- package/figma-bridge-plugin/knowledge-hub/.gitkeep +0 -0
- package/figma-bridge-plugin/knowledge-hub/uspec-references/anatomy-spec.md +0 -159
- package/figma-bridge-plugin/knowledge-hub/uspec-references/api-spec.md +0 -162
- package/figma-bridge-plugin/knowledge-hub/uspec-references/color-spec.md +0 -148
- package/figma-bridge-plugin/knowledge-hub/uspec-references/full-spec-template.md +0 -314
- package/figma-bridge-plugin/knowledge-hub/uspec-references/property-spec.md +0 -175
- package/figma-bridge-plugin/knowledge-hub/uspec-references/screen-reader-spec.md +0 -180
- package/figma-bridge-plugin/knowledge-hub/uspec-references/structure-spec.md +0 -165
- package/figma-bridge-plugin/perplexity-runner.js +0 -188
- package/figma-bridge-plugin/references/SKILL.md +0 -178
- package/figma-bridge-plugin/references/anatomy-spec.md +0 -159
- package/figma-bridge-plugin/references/api-spec.md +0 -162
- package/figma-bridge-plugin/references/color-spec.md +0 -148
- package/figma-bridge-plugin/references/full-spec-template.md +0 -314
- package/figma-bridge-plugin/references/property-spec.md +0 -175
- package/figma-bridge-plugin/references/screen-reader-spec.md +0 -180
- package/figma-bridge-plugin/references/structure-spec.md +0 -165
- package/figma-bridge-plugin/shared-prompt-config.js +0 -645
- package/figma-bridge-plugin/spec-helpers/build-table.js +0 -269
- package/figma-bridge-plugin/spec-helpers/classify-elements.js +0 -189
- package/figma-bridge-plugin/spec-helpers/index.js +0 -35
- package/figma-bridge-plugin/spec-helpers/parse-figma-link.js +0 -49
- package/figma-bridge-plugin/spec-helpers/position-markers.js +0 -158
- package/figma-bridge-plugin/stitch-auth.js +0 -322
- package/figma-bridge-plugin/stitch-runner.js +0 -1427
- package/figma-bridge-plugin/token-resolver.js +0 -107
- package/figma-bridge-plugin/ui.html +0 -4542
- package/figma-intelligence-layer/.env.example +0 -39
- package/figma-intelligence-layer/docs/local-image-generation.md +0 -60
- package/figma-intelligence-layer/examples/comfyui-workflow-template.example.json +0 -101
- package/figma-intelligence-layer/jest.config.js +0 -14
- package/figma-intelligence-layer/mcp-config.json +0 -19
- package/figma-intelligence-layer/package-lock.json +0 -5892
- package/figma-intelligence-layer/scripts/setup-comfyui-local.sh +0 -67
- package/figma-intelligence-layer/scripts/start-comfyui.sh +0 -33
- package/figma-intelligence-layer/src/index.ts +0 -2233
- package/figma-intelligence-layer/src/shared/auto-layout-validator.ts +0 -404
- package/figma-intelligence-layer/src/shared/cache.ts +0 -187
- package/figma-intelligence-layer/src/shared/color-operations.ts +0 -533
- package/figma-intelligence-layer/src/shared/color-utils.ts +0 -138
- package/figma-intelligence-layer/src/shared/component-script-builder.ts +0 -413
- package/figma-intelligence-layer/src/shared/component-templates.ts +0 -2767
- package/figma-intelligence-layer/src/shared/concept-taxonomy.ts +0 -694
- package/figma-intelligence-layer/src/shared/decision-log.ts +0 -128
- package/figma-intelligence-layer/src/shared/design-system-context.ts +0 -568
- package/figma-intelligence-layer/src/shared/design-system-intelligence.ts +0 -131
- package/figma-intelligence-layer/src/shared/design-system-matcher.ts +0 -184
- package/figma-intelligence-layer/src/shared/design-system-normalizers.ts +0 -196
- package/figma-intelligence-layer/src/shared/design-system-tokens.ts +0 -295
- package/figma-intelligence-layer/src/shared/dtcg-validator.ts +0 -530
- package/figma-intelligence-layer/src/shared/enrichment-pipeline.ts +0 -671
- package/figma-intelligence-layer/src/shared/figma-bridge.ts +0 -1418
- package/figma-intelligence-layer/src/shared/font-config.ts +0 -126
- package/figma-intelligence-layer/src/shared/icon-catalog.ts +0 -360
- package/figma-intelligence-layer/src/shared/icon-fetch.ts +0 -80
- package/figma-intelligence-layer/src/shared/prototype-script-builder.ts +0 -162
- package/figma-intelligence-layer/src/shared/response-compression.ts +0 -440
- package/figma-intelligence-layer/src/shared/semantic-token-catalog.ts +0 -324
- package/figma-intelligence-layer/src/shared/token-binder.ts +0 -505
- package/figma-intelligence-layer/src/shared/token-math.ts +0 -427
- package/figma-intelligence-layer/src/shared/token-naming.ts +0 -468
- package/figma-intelligence-layer/src/shared/token-utils.ts +0 -420
- package/figma-intelligence-layer/src/shared/types.ts +0 -346
- package/figma-intelligence-layer/src/shared/typography-presets.ts +0 -94
- package/figma-intelligence-layer/src/shared/unsplash.ts +0 -165
- package/figma-intelligence-layer/src/shared/vision-client.ts +0 -607
- package/figma-intelligence-layer/src/shared/vision-provider-anthropic.ts +0 -334
- package/figma-intelligence-layer/src/shared/vision-provider-openai.ts +0 -446
- package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/a11y-annotate-handler.ts +0 -782
- package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/a11y-annotate-renderer.ts +0 -496
- package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/a11y-annotation-kit.ts +0 -230
- package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/colorblind-sim.ts +0 -66
- package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/index.ts +0 -810
- package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/keyboard-sr-order-analyzer.ts +0 -1191
- package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/keyboard-sr-order-figma-page.ts +0 -1346
- package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/keyboard-sr-order-handler.ts +0 -148
- package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/vpat-figma-page.ts +0 -499
- package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/vpat-report.ts +0 -910
- package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/wcag-checker.ts +0 -989
- package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/wcag-criteria.ts +0 -1160
- package/figma-intelligence-layer/src/tools/phase1-vision/design-from-ref/index.ts +0 -424
- package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/component-recognizer.ts +0 -38
- package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/ds-matcher.ts +0 -111
- package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/font-matcher.ts +0 -114
- package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/icon-resolver.ts +0 -103
- package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/index.ts +0 -1060
- package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/layout-segmenter.ts +0 -18
- package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/token-inferencer.ts +0 -39
- package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/vision-pipeline.ts +0 -58
- package/figma-intelligence-layer/src/tools/phase1-vision/sketch-to-design/index.ts +0 -298
- package/figma-intelligence-layer/src/tools/phase1-vision/visual-audit/index.ts +0 -197
- package/figma-intelligence-layer/src/tools/phase2-accuracy/component-audit/index.ts +0 -494
- package/figma-intelligence-layer/src/tools/phase2-accuracy/intent-translator/index.ts +0 -356
- package/figma-intelligence-layer/src/tools/phase2-accuracy/layout-intelligence/container-patterns.ts +0 -123
- package/figma-intelligence-layer/src/tools/phase2-accuracy/layout-intelligence/index.ts +0 -663
- package/figma-intelligence-layer/src/tools/phase2-accuracy/lint-rules/built-in-rules.yaml +0 -56
- package/figma-intelligence-layer/src/tools/phase2-accuracy/lint-rules/index.ts +0 -614
- package/figma-intelligence-layer/src/tools/phase2-accuracy/lint-rules/rule-engine.ts +0 -113
- package/figma-intelligence-layer/src/tools/phase2-accuracy/theme-generator/color-theory.ts +0 -178
- package/figma-intelligence-layer/src/tools/phase2-accuracy/theme-generator/index.ts +0 -470
- package/figma-intelligence-layer/src/tools/phase2-accuracy/variant-expander/index.ts +0 -429
- package/figma-intelligence-layer/src/tools/phase2-accuracy/variant-expander/token-override-maps.ts +0 -226
- package/figma-intelligence-layer/src/tools/phase3-generation/ai-image-insert/index.ts +0 -535
- package/figma-intelligence-layer/src/tools/phase3-generation/component-archaeologist/index.ts +0 -660
- package/figma-intelligence-layer/src/tools/phase3-generation/component-archaeologist/pattern-fingerprints.ts +0 -209
- package/figma-intelligence-layer/src/tools/phase3-generation/composition-builder/index.ts +0 -540
- package/figma-intelligence-layer/src/tools/phase3-generation/figma-animated-build.ts +0 -391
- package/figma-intelligence-layer/src/tools/phase3-generation/page-architect/index.ts +0 -2019
- package/figma-intelligence-layer/src/tools/phase3-generation/page-architect/screen-templates.ts +0 -131
- package/figma-intelligence-layer/src/tools/phase3-generation/prototype-map/index.ts +0 -381
- package/figma-intelligence-layer/src/tools/phase3-generation/prototype-wire/index.ts +0 -565
- package/figma-intelligence-layer/src/tools/phase3-generation/swarm-build/index.ts +0 -764
- package/figma-intelligence-layer/src/tools/phase3-generation/system-drift/index.ts +0 -535
- package/figma-intelligence-layer/src/tools/phase3-generation/unsplash-search/index.ts +0 -84
- package/figma-intelligence-layer/src/tools/phase3-generation/url-to-frame/index.ts +0 -401
- package/figma-intelligence-layer/src/tools/phase4-sync/animation-specifier/code-generators/css-animations.ts +0 -68
- package/figma-intelligence-layer/src/tools/phase4-sync/animation-specifier/code-generators/framer-motion.ts +0 -78
- package/figma-intelligence-layer/src/tools/phase4-sync/animation-specifier/code-generators/swift-animations.ts +0 -93
- package/figma-intelligence-layer/src/tools/phase4-sync/animation-specifier/index.ts +0 -596
- package/figma-intelligence-layer/src/tools/phase4-sync/ci-check/index.ts +0 -462
- package/figma-intelligence-layer/src/tools/phase4-sync/export-tokens/index.ts +0 -1470
- package/figma-intelligence-layer/src/tools/phase4-sync/generate-component-code/index.ts +0 -829
- package/figma-intelligence-layer/src/tools/phase4-sync/handoff-spec/index.ts +0 -702
- package/figma-intelligence-layer/src/tools/phase4-sync/icon-library-sync/index.ts +0 -483
- package/figma-intelligence-layer/src/tools/phase4-sync/sync-from-code/index.ts +0 -501
- package/figma-intelligence-layer/src/tools/phase4-sync/sync-from-code/storybook-parser.ts +0 -106
- package/figma-intelligence-layer/src/tools/phase4-sync/watch-docs/index.ts +0 -676
- package/figma-intelligence-layer/src/tools/phase4-sync/webhook-listener/index.ts +0 -560
- package/figma-intelligence-layer/src/tools/phase5-governance/apg-doc/index.ts +0 -1043
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/component-detection.ts +0 -620
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/anatomy.ts +0 -331
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/color-tokens.ts +0 -77
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/properties.ts +0 -54
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/snapshot.ts +0 -287
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/spacing.ts +0 -71
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/states.ts +0 -43
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/typography.ts +0 -71
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/index.ts +0 -221
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/_default.ts +0 -166
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/accordion.ts +0 -232
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/alert.ts +0 -234
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/avatar-group.ts +0 -270
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/avatar.ts +0 -249
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/badge.ts +0 -231
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/banner.ts +0 -293
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/breadcrumb.ts +0 -240
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/button.ts +0 -243
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/calendar.ts +0 -307
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/card.ts +0 -143
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/checkbox.ts +0 -227
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/chip.ts +0 -233
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/combobox.ts +0 -282
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/datepicker.ts +0 -276
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/divider.ts +0 -223
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/drawer.ts +0 -255
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/dropdown-menu.ts +0 -289
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/empty-state.ts +0 -261
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/file-uploader.ts +0 -290
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/form.ts +0 -265
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/grid.ts +0 -238
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/icon.ts +0 -255
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/index.ts +0 -128
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/inline-edit.ts +0 -286
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/inline-message.ts +0 -255
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/input.ts +0 -330
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/link.ts +0 -247
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/list.ts +0 -250
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/menu.ts +0 -247
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/modal.ts +0 -144
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/navbar.ts +0 -264
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/navigation.ts +0 -251
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/number-input.ts +0 -261
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/pagination.ts +0 -248
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/popover.ts +0 -270
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/progress.ts +0 -251
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/radio.ts +0 -142
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/range-slider.ts +0 -282
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/rating.ts +0 -250
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/search.ts +0 -258
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/segmented-control.ts +0 -265
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/select.ts +0 -319
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/skeleton.ts +0 -256
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/slider.ts +0 -232
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/spinner.ts +0 -239
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/status-dot.ts +0 -252
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/stepper.ts +0 -270
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/table.ts +0 -244
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/tabs.ts +0 -143
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/tag.ts +0 -243
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/textarea.ts +0 -259
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/time-picker.ts +0 -293
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/toast.ts +0 -144
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/toggle.ts +0 -289
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/toolbar.ts +0 -267
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/tooltip.ts +0 -232
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/treeview.ts +0 -257
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/typography.ts +0 -319
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/legacy-compat.ts +0 -121
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/renderers/anatomy-diagram.ts +0 -430
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/renderers/figma-page.ts +0 -312
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/renderers/json.ts +0 -129
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/renderers/markdown.ts +0 -78
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/renderers/visual-doc.ts +0 -2333
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/accessibility.ts +0 -100
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/anatomy.ts +0 -32
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/color-tokens.ts +0 -59
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/content-guidance.ts +0 -18
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/design-tokens.ts +0 -53
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/interaction-rules.ts +0 -19
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/overview.ts +0 -91
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/properties-api.ts +0 -71
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/qa-criteria.ts +0 -19
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/related-components.ts +0 -110
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/responsive.ts +0 -19
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/size-specs.ts +0 -67
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/spacing-structure.ts +0 -58
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/state-specs.ts +0 -79
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/states.ts +0 -50
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/type-hierarchy.ts +0 -33
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/typography.ts +0 -55
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/usage-guidelines.ts +0 -73
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/variants.ts +0 -81
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/types.ts +0 -409
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec-sheet/index.ts +0 -198
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec-sheet/renderer.ts +0 -701
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec-sheet/types.ts +0 -88
- package/figma-intelligence-layer/src/tools/phase5-governance/decision-log/index.ts +0 -135
- package/figma-intelligence-layer/src/tools/phase5-governance/design-decision-log/index.ts +0 -491
- package/figma-intelligence-layer/src/tools/phase5-governance/ds-primitives/index.ts +0 -416
- package/figma-intelligence-layer/src/tools/phase5-governance/ds-scaffolder/index.ts +0 -722
- package/figma-intelligence-layer/src/tools/phase5-governance/ds-variables/index.ts +0 -449
- package/figma-intelligence-layer/src/tools/phase5-governance/health-report/index.ts +0 -393
- package/figma-intelligence-layer/src/tools/phase5-governance/taxonomy-docs/index.ts +0 -406
- package/figma-intelligence-layer/src/tools/phase5-governance/taxonomy-docs/renderers/figma-page.ts +0 -292
- package/figma-intelligence-layer/src/tools/phase5-governance/taxonomy-docs/renderers/json.ts +0 -24
- package/figma-intelligence-layer/src/tools/phase5-governance/taxonomy-docs/renderers/markdown.ts +0 -172
- package/figma-intelligence-layer/src/tools/phase5-governance/taxonomy-docs/renderers/naming-guide.ts +0 -409
- package/figma-intelligence-layer/src/tools/phase5-governance/token-analytics/index.ts +0 -594
- package/figma-intelligence-layer/src/tools/phase5-governance/token-docs/index.ts +0 -710
- package/figma-intelligence-layer/src/tools/phase5-governance/token-migrate/index.ts +0 -458
- package/figma-intelligence-layer/src/tools/phase5-governance/token-naming/index.ts +0 -134
- package/figma-intelligence-layer/tests/apg-doc.test.ts +0 -101
- package/figma-intelligence-layer/tests/design-system-context.test.ts +0 -152
- package/figma-intelligence-layer/tests/design-system-matcher.test.ts +0 -144
- package/figma-intelligence-layer/tests/figma-bridge.test.ts +0 -83
- package/figma-intelligence-layer/tests/generate-image-and-insert.test.ts +0 -56
- package/figma-intelligence-layer/tests/screen-cloner-regression.test.ts +0 -69
- package/figma-intelligence-layer/tests/smoke.test.ts +0 -174
- package/figma-intelligence-layer/tests/spec-generator.test.ts +0 -127
- package/figma-intelligence-layer/tests/token-migrate.test.ts +0 -21
- package/figma-intelligence-layer/tests/token-naming.test.ts +0 -30
- package/figma-intelligence-layer/tsconfig.json +0 -19
- package/scripts/clean-existing-chunks.js +0 -179
- package/scripts/connect-ai-tool.js +0 -490
- package/scripts/convert-hub-pdfs.js +0 -425
- package/scripts/figma-mcp-status.js +0 -349
- package/scripts/register-codex-mcp.js +0 -96
- /package/{design-bridge → dist/design-bridge}/.env.example +0 -0
- /package/{design-bridge → dist/design-bridge}/package.json +0 -0
- /package/{figma-bridge-plugin → dist/figma-bridge-plugin}/manifest.json +0 -0
- /package/{figma-bridge-plugin → dist/figma-bridge-plugin}/package.json +0 -0
- /package/{figma-intelligence-layer → dist/figma-intelligence-layer}/package.json +0 -0
|
@@ -1,1043 +0,0 @@
|
|
|
1
|
-
import { decisionLog } from "../../../shared/decision-log.js";
|
|
2
|
-
import { getBridge } from "../../../shared/figma-bridge.js";
|
|
3
|
-
import {
|
|
4
|
-
captureSnapshot,
|
|
5
|
-
createDocumentationPages,
|
|
6
|
-
formatDocumentReport,
|
|
7
|
-
resolveTargetNodeId,
|
|
8
|
-
} from "../component-spec/index.js";
|
|
9
|
-
import type {
|
|
10
|
-
GeneratedDocument,
|
|
11
|
-
NodeSnapshot,
|
|
12
|
-
} from "../component-spec/types.js";
|
|
13
|
-
|
|
14
|
-
export interface FigmaApgDocArgs {
|
|
15
|
-
nodeId?: string;
|
|
16
|
-
patternHint?: string;
|
|
17
|
-
framework?: "html" | "react" | "vue" | "angular";
|
|
18
|
-
outputFormat: "json" | "report" | "figma-page" | "all";
|
|
19
|
-
includeCodeExamples?: boolean;
|
|
20
|
-
writeToDescription?: boolean;
|
|
21
|
-
descriptionMode?: "replace" | "append";
|
|
22
|
-
pageName?: string;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
type ApgPatternId =
|
|
26
|
-
| "button"
|
|
27
|
-
| "menu-button"
|
|
28
|
-
| "dialog-modal"
|
|
29
|
-
| "tabs"
|
|
30
|
-
| "accordion"
|
|
31
|
-
| "combobox"
|
|
32
|
-
| "listbox"
|
|
33
|
-
| "checkbox"
|
|
34
|
-
| "radio-group"
|
|
35
|
-
| "switch"
|
|
36
|
-
| "slider"
|
|
37
|
-
| "toolbar"
|
|
38
|
-
| "grid"
|
|
39
|
-
| "treeview";
|
|
40
|
-
|
|
41
|
-
interface ApgPatternDefinition {
|
|
42
|
-
id: ApgPatternId;
|
|
43
|
-
title: string;
|
|
44
|
-
aliases: string[];
|
|
45
|
-
url: string;
|
|
46
|
-
summary: string;
|
|
47
|
-
nativeFirst: string[];
|
|
48
|
-
accessibleName: string[];
|
|
49
|
-
roles: string[];
|
|
50
|
-
requiredStates: string[];
|
|
51
|
-
optionalStates: string[];
|
|
52
|
-
forbiddenPatterns: string[];
|
|
53
|
-
keyboard: string[];
|
|
54
|
-
focus: string[];
|
|
55
|
-
implementationNotes: string[];
|
|
56
|
-
testing: string[];
|
|
57
|
-
exampleSnippets: Partial<Record<NonNullable<FigmaApgDocArgs["framework"]>, string>>;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export interface FigmaApgDocResult {
|
|
61
|
-
pattern: {
|
|
62
|
-
id: ApgPatternId;
|
|
63
|
-
title: string;
|
|
64
|
-
url: string;
|
|
65
|
-
confidence: number;
|
|
66
|
-
matchedBy: string[];
|
|
67
|
-
};
|
|
68
|
-
target: {
|
|
69
|
-
nodeId: string;
|
|
70
|
-
name: string;
|
|
71
|
-
type: string;
|
|
72
|
-
};
|
|
73
|
-
report?: string;
|
|
74
|
-
figmaPageId?: string;
|
|
75
|
-
descriptionUpdated?: boolean;
|
|
76
|
-
warnings?: string[];
|
|
77
|
-
document: GeneratedDocument;
|
|
78
|
-
implementationSnippet?: string;
|
|
79
|
-
logEntryId: string;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const GENERAL_REFERENCES = [
|
|
83
|
-
"APG names and descriptions: https://www.w3.org/WAI/ARIA/apg/practices/names-and-descriptions/",
|
|
84
|
-
"ARIA in HTML: https://www.w3.org/TR/aria-in-html/",
|
|
85
|
-
"Using ARIA: https://www.w3.org/TR/using-aria/",
|
|
86
|
-
];
|
|
87
|
-
|
|
88
|
-
const APG_PATTERNS: ApgPatternDefinition[] = [
|
|
89
|
-
{
|
|
90
|
-
id: "button",
|
|
91
|
-
title: "Button",
|
|
92
|
-
aliases: ["button", "cta", "action", "icon button"],
|
|
93
|
-
url: "https://www.w3.org/WAI/ARIA/apg/patterns/button/",
|
|
94
|
-
summary: "Button triggers an action immediately and should use the native button element whenever possible.",
|
|
95
|
-
nativeFirst: [
|
|
96
|
-
"Use a native <button> element for actions instead of a generic container with role=button.",
|
|
97
|
-
"Use a native <a> only when the control navigates to a new location rather than triggering an in-place action.",
|
|
98
|
-
],
|
|
99
|
-
accessibleName: [
|
|
100
|
-
"Prefer visible button text as the accessible name.",
|
|
101
|
-
"For icon-only buttons, provide an accessible name with aria-label or aria-labelledby.",
|
|
102
|
-
"Keep the accessible name stable across loading and pressed states unless the action meaning changes.",
|
|
103
|
-
],
|
|
104
|
-
roles: ["button"],
|
|
105
|
-
requiredStates: ["Disabled state should map to the native disabled attribute when applicable."],
|
|
106
|
-
optionalStates: ["aria-pressed for toggle buttons only."],
|
|
107
|
-
forbiddenPatterns: [
|
|
108
|
-
"Do not use role=button on a div when a native button is available.",
|
|
109
|
-
"Do not add aria-pressed to a command button that is not toggleable.",
|
|
110
|
-
],
|
|
111
|
-
keyboard: ["Tab moves focus to the button.", "Space and Enter activate the button."],
|
|
112
|
-
focus: [
|
|
113
|
-
"Focus stays on the button after activation unless the action opens a new context such as a dialog.",
|
|
114
|
-
"Document the visible focus ring and make sure it survives all themes and densities.",
|
|
115
|
-
],
|
|
116
|
-
implementationNotes: [
|
|
117
|
-
"Support disabled, loading, and pressed states without changing semantics unexpectedly.",
|
|
118
|
-
"Decorative icons should be hidden from assistive technologies when the visible label already communicates meaning.",
|
|
119
|
-
],
|
|
120
|
-
testing: [
|
|
121
|
-
"Verify activation with Enter and Space.",
|
|
122
|
-
"Check that icon-only variants expose a meaningful accessible name.",
|
|
123
|
-
"Confirm disabled buttons are announced correctly and cannot be activated.",
|
|
124
|
-
],
|
|
125
|
-
exampleSnippets: {
|
|
126
|
-
html: `<button type="button" aria-label="Save changes">\n <svg aria-hidden="true" focusable="false"></svg>\n</button>`,
|
|
127
|
-
react: `<button type="button" aria-label="Save changes">\n <SaveIcon aria-hidden="true" focusable="false" />\n</button>`,
|
|
128
|
-
},
|
|
129
|
-
},
|
|
130
|
-
{
|
|
131
|
-
id: "menu-button",
|
|
132
|
-
title: "Menu Button",
|
|
133
|
-
aliases: ["menu button", "overflow menu", "kebab", "more", "actions menu"],
|
|
134
|
-
url: "https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/",
|
|
135
|
-
summary: "Menu button opens a menu of commands and needs explicit expanded state and menu ownership wiring.",
|
|
136
|
-
nativeFirst: [
|
|
137
|
-
"Use a native <button> as the trigger and layer ARIA on top for the popup relationship.",
|
|
138
|
-
"If the popup is simple site navigation, consider a disclosure pattern before reaching for menu semantics.",
|
|
139
|
-
],
|
|
140
|
-
accessibleName: [
|
|
141
|
-
"The trigger needs a clear action-oriented name such as More actions or Column options.",
|
|
142
|
-
"Reference visible text with aria-labelledby when available instead of duplicating with aria-label.",
|
|
143
|
-
],
|
|
144
|
-
roles: ["button trigger", "menu", "menuitem"],
|
|
145
|
-
requiredStates: ["aria-haspopup=menu", "aria-expanded on the trigger", "aria-controls when you reference the popup element"],
|
|
146
|
-
optionalStates: ["aria-disabled on menuitem when an item is unavailable."],
|
|
147
|
-
forbiddenPatterns: [
|
|
148
|
-
"Do not use menu semantics for a list of page links that behaves like ordinary navigation.",
|
|
149
|
-
"Do not leave focus behind on the trigger once the menu is open.",
|
|
150
|
-
],
|
|
151
|
-
keyboard: [
|
|
152
|
-
"Enter or Space opens the menu and moves focus into the first menu item.",
|
|
153
|
-
"Arrow Down typically opens the menu and places focus on the first item.",
|
|
154
|
-
"Escape closes the menu and returns focus to the trigger.",
|
|
155
|
-
],
|
|
156
|
-
focus: [
|
|
157
|
-
"When the menu opens, move focus into the menu.",
|
|
158
|
-
"When it closes, restore focus to the trigger unless another user action moved it elsewhere.",
|
|
159
|
-
],
|
|
160
|
-
implementationNotes: [
|
|
161
|
-
"Keep the trigger label stable while expanded state changes are conveyed through aria-expanded.",
|
|
162
|
-
"Use roving tabindex or managed focus for menu items; avoid leaving every item in the tab order.",
|
|
163
|
-
],
|
|
164
|
-
testing: [
|
|
165
|
-
"Verify focus return on Escape and item selection.",
|
|
166
|
-
"Check arrow-key navigation across all menu items.",
|
|
167
|
-
"Confirm screen readers announce the trigger as a menu button with expanded/collapsed state.",
|
|
168
|
-
],
|
|
169
|
-
exampleSnippets: {
|
|
170
|
-
html: `<button aria-haspopup="menu" aria-expanded="false" aria-controls="actions-menu">\n More actions\n</button>\n<ul id="actions-menu" role="menu" hidden>\n <li role="menuitem" tabindex="-1">Rename</li>\n</ul>`,
|
|
171
|
-
react: `<button aria-haspopup="menu" aria-expanded={open} aria-controls="actions-menu">\n More actions\n</button>`,
|
|
172
|
-
},
|
|
173
|
-
},
|
|
174
|
-
{
|
|
175
|
-
id: "dialog-modal",
|
|
176
|
-
title: "Dialog (Modal)",
|
|
177
|
-
aliases: ["dialog", "modal", "sheet", "drawer", "popover dialog"],
|
|
178
|
-
url: "https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/",
|
|
179
|
-
summary: "Modal dialog interrupts the current workflow and must manage initial focus, containment, and focus return.",
|
|
180
|
-
nativeFirst: [
|
|
181
|
-
"Prefer a native dialog implementation when your platform support and framework abstractions are mature enough.",
|
|
182
|
-
"Use ARIA dialog semantics only when you also implement the required focus management behavior.",
|
|
183
|
-
],
|
|
184
|
-
accessibleName: [
|
|
185
|
-
"Dialogs need an accessible name, usually from the visible title via aria-labelledby.",
|
|
186
|
-
"Use aria-describedby for supporting context when the dialog body provides essential orientation or warning text.",
|
|
187
|
-
],
|
|
188
|
-
roles: ["dialog"],
|
|
189
|
-
requiredStates: ["aria-modal=true", "Accessible name via aria-labelledby or aria-label"],
|
|
190
|
-
optionalStates: ["aria-describedby when there is critical supporting text to announce."],
|
|
191
|
-
forbiddenPatterns: [
|
|
192
|
-
"Do not leave keyboard focus outside the modal while it is open.",
|
|
193
|
-
"Do not mark a dialog modal if background content remains interactive.",
|
|
194
|
-
],
|
|
195
|
-
keyboard: [
|
|
196
|
-
"Tab and Shift+Tab cycle within the dialog.",
|
|
197
|
-
"Escape closes the dialog when the workflow allows cancellation.",
|
|
198
|
-
],
|
|
199
|
-
focus: [
|
|
200
|
-
"Move focus to the most appropriate element when the dialog opens.",
|
|
201
|
-
"Return focus to the invoking control when the dialog closes unless the workflow dictates a better destination.",
|
|
202
|
-
],
|
|
203
|
-
implementationNotes: [
|
|
204
|
-
"Choose the initial focus target intentionally: title, first field, or least destructive action depending on the task.",
|
|
205
|
-
"Alert dialogs often need a stronger announcement and tighter action choices than standard dialogs.",
|
|
206
|
-
],
|
|
207
|
-
testing: [
|
|
208
|
-
"Verify background content is inert to keyboard and pointer interaction while the dialog is open.",
|
|
209
|
-
"Check that focus never escapes the modal until it closes.",
|
|
210
|
-
"Confirm the dialog title and description are announced once on open.",
|
|
211
|
-
],
|
|
212
|
-
exampleSnippets: {
|
|
213
|
-
html: `<div role="dialog" aria-modal="true" aria-labelledby="dialog-title" aria-describedby="dialog-body">\n <h2 id="dialog-title">Delete file</h2>\n <p id="dialog-body">This action cannot be undone.</p>\n</div>`,
|
|
214
|
-
react: `<div role="dialog" aria-modal="true" aria-labelledby={titleId} aria-describedby={bodyId}>\n ...\n</div>`,
|
|
215
|
-
},
|
|
216
|
-
},
|
|
217
|
-
{
|
|
218
|
-
id: "tabs",
|
|
219
|
-
title: "Tabs",
|
|
220
|
-
aliases: ["tabs", "tab", "segmented control", "tablist"],
|
|
221
|
-
url: "https://www.w3.org/WAI/ARIA/apg/patterns/tabs/",
|
|
222
|
-
summary: "Tabs organize sibling panels and require tab, tablist, and tabpanel relationships with keyboard navigation.",
|
|
223
|
-
nativeFirst: [
|
|
224
|
-
"If the UI is effectively page navigation, prefer links and headings over tab semantics.",
|
|
225
|
-
"Use button elements for tabs when you need in-place panel switching.",
|
|
226
|
-
],
|
|
227
|
-
accessibleName: [
|
|
228
|
-
"Each tab needs a concise visible label that also serves as the accessible name.",
|
|
229
|
-
"The tablist itself may need an accessible name when multiple tablists exist on the same view.",
|
|
230
|
-
],
|
|
231
|
-
roles: ["tablist", "tab", "tabpanel"],
|
|
232
|
-
requiredStates: ["aria-selected on the active tab", "aria-controls linking each tab to its panel"],
|
|
233
|
-
optionalStates: ["aria-orientation when the tablist is vertical."],
|
|
234
|
-
forbiddenPatterns: [
|
|
235
|
-
"Do not place every tab panel in the tab order when hidden.",
|
|
236
|
-
"Do not use tabs when panels are not siblings in the same context.",
|
|
237
|
-
],
|
|
238
|
-
keyboard: [
|
|
239
|
-
"Tab enters or exits the tablist depending on the current focus model.",
|
|
240
|
-
"Arrow keys move focus between tabs.",
|
|
241
|
-
"Enter or Space activates a tab in manual activation models.",
|
|
242
|
-
"Home and End optionally jump to the first and last tabs.",
|
|
243
|
-
],
|
|
244
|
-
focus: [
|
|
245
|
-
"Use roving tabindex so only one tab is tabbable at a time.",
|
|
246
|
-
"Keep focus on the active tab unless activation explicitly moves focus into the panel.",
|
|
247
|
-
],
|
|
248
|
-
implementationNotes: [
|
|
249
|
-
"Document whether the component uses automatic or manual tab activation.",
|
|
250
|
-
"Ensure hidden panels are not reachable by screen reader cursor navigation in ways that confuse context.",
|
|
251
|
-
],
|
|
252
|
-
testing: [
|
|
253
|
-
"Verify arrow-key navigation wraps or clamps consistently with the documented behavior.",
|
|
254
|
-
"Check that the active tab exposes aria-selected=true and its panel is correctly linked.",
|
|
255
|
-
"Confirm hidden panels are not announced as active content.",
|
|
256
|
-
],
|
|
257
|
-
exampleSnippets: {
|
|
258
|
-
html: `<div role="tablist" aria-label="Billing views">\n <button role="tab" aria-selected="true" aria-controls="panel-overview" id="tab-overview">Overview</button>\n</div>\n<section id="panel-overview" role="tabpanel" aria-labelledby="tab-overview"></section>`,
|
|
259
|
-
react: `<div role="tablist" aria-label="Billing views">{tabs.map(...)}</div>`,
|
|
260
|
-
},
|
|
261
|
-
},
|
|
262
|
-
{
|
|
263
|
-
id: "accordion",
|
|
264
|
-
title: "Accordion",
|
|
265
|
-
aliases: ["accordion", "disclosure", "expand", "collapse"],
|
|
266
|
-
url: "https://www.w3.org/WAI/ARIA/apg/patterns/accordion/",
|
|
267
|
-
summary: "Accordion reveals and hides related sections with header buttons that control associated panels.",
|
|
268
|
-
nativeFirst: [
|
|
269
|
-
"Use a real button for each accordion header trigger.",
|
|
270
|
-
"Consider native details/summary when the simpler browser behavior fits the product and design constraints.",
|
|
271
|
-
],
|
|
272
|
-
accessibleName: [
|
|
273
|
-
"The header button should use its visible heading text as the accessible name.",
|
|
274
|
-
"Avoid repeating the expanded or collapsed state in the accessible name; that belongs in aria-expanded.",
|
|
275
|
-
],
|
|
276
|
-
roles: ["button", "region when appropriate"],
|
|
277
|
-
requiredStates: ["aria-expanded on each trigger", "aria-controls linking trigger to panel"],
|
|
278
|
-
optionalStates: ["role=region on panels when doing so improves structure and does not create landmark overload."],
|
|
279
|
-
forbiddenPatterns: [
|
|
280
|
-
"Do not use non-focusable text as the disclosure trigger.",
|
|
281
|
-
"Do not duplicate state words like expanded in the accessible name.",
|
|
282
|
-
],
|
|
283
|
-
keyboard: [
|
|
284
|
-
"Tab moves between accordion headers and any focusable content inside expanded panels.",
|
|
285
|
-
"Enter and Space toggle the current header.",
|
|
286
|
-
"Optional arrow-key navigation may move between headers.",
|
|
287
|
-
],
|
|
288
|
-
focus: [
|
|
289
|
-
"Activation typically leaves focus on the header button.",
|
|
290
|
-
"If only one panel may stay open, document what happens to focus when another panel closes.",
|
|
291
|
-
],
|
|
292
|
-
implementationNotes: [
|
|
293
|
-
"Decide whether multiple sections may be open at once and document that behavior clearly.",
|
|
294
|
-
"If panel content starts with a form field or destructive action, consider whether focus should remain on the header or move intentionally.",
|
|
295
|
-
],
|
|
296
|
-
testing: [
|
|
297
|
-
"Check that aria-expanded changes on every toggle.",
|
|
298
|
-
"Verify each header is keyboard-operable and exposes a clear accessible name.",
|
|
299
|
-
"Confirm collapsed content is not reachable unexpectedly.",
|
|
300
|
-
],
|
|
301
|
-
exampleSnippets: {
|
|
302
|
-
html: `<h3>\n <button aria-expanded="false" aria-controls="faq-panel-1" id="faq-trigger-1">Shipping details</button>\n</h3>\n<div id="faq-panel-1" aria-labelledby="faq-trigger-1" hidden></div>`,
|
|
303
|
-
},
|
|
304
|
-
},
|
|
305
|
-
{
|
|
306
|
-
id: "combobox",
|
|
307
|
-
title: "Combobox",
|
|
308
|
-
aliases: ["combobox", "autocomplete", "typeahead", "search select", "picker"],
|
|
309
|
-
url: "https://www.w3.org/WAI/ARIA/apg/patterns/combobox/",
|
|
310
|
-
summary: "Combobox combines text input or selection with a popup and needs explicit relationships between the field and popup.",
|
|
311
|
-
nativeFirst: [
|
|
312
|
-
"Prefer native select for simple one-of-many selection when freeform input is not needed.",
|
|
313
|
-
"Use a real input element when users type to filter or search options.",
|
|
314
|
-
],
|
|
315
|
-
accessibleName: [
|
|
316
|
-
"Use a visible label associated with the input whenever possible.",
|
|
317
|
-
"Use aria-describedby for helper or validation text instead of packing everything into the accessible name.",
|
|
318
|
-
],
|
|
319
|
-
roles: ["combobox", "listbox or grid popup", "option"],
|
|
320
|
-
requiredStates: ["aria-expanded", "aria-controls", "Accessible name via label, aria-labelledby, or aria-label"],
|
|
321
|
-
optionalStates: ["aria-activedescendant when focus remains on the input while highlighting popup options.", "aria-autocomplete when the behavior matches inline, list, or both."],
|
|
322
|
-
forbiddenPatterns: [
|
|
323
|
-
"Do not replace a simple select with a complex combobox unless the product needs it.",
|
|
324
|
-
"Do not move DOM focus into the popup if your implementation model relies on aria-activedescendant.",
|
|
325
|
-
],
|
|
326
|
-
keyboard: [
|
|
327
|
-
"Typing updates the input and may filter options.",
|
|
328
|
-
"Arrow Down opens the popup or moves through available options.",
|
|
329
|
-
"Enter commits the active option when appropriate.",
|
|
330
|
-
"Escape closes the popup and may clear the current pending selection depending on the design.",
|
|
331
|
-
],
|
|
332
|
-
focus: [
|
|
333
|
-
"Choose one focus model and document it: DOM focus on the input with aria-activedescendant, or managed focus in the popup.",
|
|
334
|
-
"Ensure focus return and screen reader announcements stay consistent when the popup opens and closes.",
|
|
335
|
-
],
|
|
336
|
-
implementationNotes: [
|
|
337
|
-
"Combobox is one of the most failure-prone ARIA patterns; use it only when native controls do not meet the requirement.",
|
|
338
|
-
"Validation, loading, no-results, and async states need explicit announcements and documentation.",
|
|
339
|
-
],
|
|
340
|
-
testing: [
|
|
341
|
-
"Verify label association, expanded state, and popup ownership.",
|
|
342
|
-
"Check arrow-key navigation, selection, and screen reader announcements in both collapsed and expanded states.",
|
|
343
|
-
"Test loading, empty, and error states with assistive technologies.",
|
|
344
|
-
],
|
|
345
|
-
exampleSnippets: {
|
|
346
|
-
html: `<label for="city">City</label>\n<input id="city" role="combobox" aria-expanded="false" aria-controls="city-listbox" aria-autocomplete="list" />\n<ul id="city-listbox" role="listbox" hidden></ul>`,
|
|
347
|
-
react: `<input role="combobox" aria-expanded={open} aria-controls={listboxId} aria-activedescendant={activeId} />`,
|
|
348
|
-
},
|
|
349
|
-
},
|
|
350
|
-
{
|
|
351
|
-
id: "listbox",
|
|
352
|
-
title: "Listbox",
|
|
353
|
-
aliases: ["listbox", "select", "single select", "multi select", "picker list"],
|
|
354
|
-
url: "https://www.w3.org/WAI/ARIA/apg/patterns/listbox/",
|
|
355
|
-
summary: "Listbox presents a set of selectable options and is best reserved for custom selection UIs that cannot use native select.",
|
|
356
|
-
nativeFirst: [
|
|
357
|
-
"Use native select whenever browser behavior is acceptable.",
|
|
358
|
-
"Only use listbox semantics for truly custom selection experiences.",
|
|
359
|
-
],
|
|
360
|
-
accessibleName: [
|
|
361
|
-
"The listbox itself needs an accessible name from a visible label, aria-labelledby, or aria-label.",
|
|
362
|
-
"Option text should be concise and stable so the selected item is announced clearly.",
|
|
363
|
-
],
|
|
364
|
-
roles: ["listbox", "option"],
|
|
365
|
-
requiredStates: ["aria-selected on selected options when the pattern requires it."],
|
|
366
|
-
optionalStates: ["aria-multiselectable=true for multi-select listboxes."],
|
|
367
|
-
forbiddenPatterns: [
|
|
368
|
-
"Do not put unrelated interactive elements inside options.",
|
|
369
|
-
"Do not create a custom listbox when a simple select meets the requirement.",
|
|
370
|
-
],
|
|
371
|
-
keyboard: [
|
|
372
|
-
"Arrow keys move focus between options.",
|
|
373
|
-
"Space may toggle selection in multi-select models.",
|
|
374
|
-
"Typeahead is recommended for longer option lists.",
|
|
375
|
-
],
|
|
376
|
-
focus: [
|
|
377
|
-
"Use a documented focus model: roving tabindex or aria-activedescendant.",
|
|
378
|
-
"The selected option should remain discoverable and consistently announced.",
|
|
379
|
-
],
|
|
380
|
-
implementationNotes: [
|
|
381
|
-
"Document single-select versus multi-select behavior explicitly.",
|
|
382
|
-
"Large or virtualized lists need extra care so assistive technologies still perceive the active option correctly.",
|
|
383
|
-
],
|
|
384
|
-
testing: [
|
|
385
|
-
"Verify label announcement, option navigation, and selected state announcements.",
|
|
386
|
-
"Check multi-select shortcuts if supported.",
|
|
387
|
-
"Confirm virtualized options remain understandable to assistive technologies.",
|
|
388
|
-
],
|
|
389
|
-
exampleSnippets: {
|
|
390
|
-
html: `<label id="assignee-label">Assignee</label>\n<ul role="listbox" aria-labelledby="assignee-label">\n <li role="option" aria-selected="true">Alex</li>\n</ul>`,
|
|
391
|
-
},
|
|
392
|
-
},
|
|
393
|
-
{
|
|
394
|
-
id: "checkbox",
|
|
395
|
-
title: "Checkbox",
|
|
396
|
-
aliases: ["checkbox", "check box", "tick"],
|
|
397
|
-
url: "https://www.w3.org/WAI/ARIA/apg/patterns/checkbox/",
|
|
398
|
-
summary: "Checkbox represents an independent on/off choice and should generally stay native.",
|
|
399
|
-
nativeFirst: [
|
|
400
|
-
"Use a native input type=checkbox whenever possible.",
|
|
401
|
-
"If you create a custom visual wrapper, keep the real checkbox in the accessibility tree.",
|
|
402
|
-
],
|
|
403
|
-
accessibleName: [
|
|
404
|
-
"Associate the checkbox with visible label text.",
|
|
405
|
-
"Keep the label focused on the option meaning rather than the state.",
|
|
406
|
-
],
|
|
407
|
-
roles: ["checkbox"],
|
|
408
|
-
requiredStates: ["aria-checked when not using a native checkbox element."],
|
|
409
|
-
optionalStates: ["Mixed state when the control genuinely represents a partial selection."],
|
|
410
|
-
forbiddenPatterns: [
|
|
411
|
-
"Do not use checkbox for mutually exclusive options.",
|
|
412
|
-
"Do not hide the label and rely on title text alone.",
|
|
413
|
-
],
|
|
414
|
-
keyboard: ["Tab moves focus to the checkbox.", "Space toggles checked state."],
|
|
415
|
-
focus: [
|
|
416
|
-
"Focus remains on the checkbox after toggle.",
|
|
417
|
-
"Document the visual focus indicator separately from the checked state styling.",
|
|
418
|
-
],
|
|
419
|
-
implementationNotes: [
|
|
420
|
-
"If the checkbox drives nested selections, document when a mixed state appears.",
|
|
421
|
-
"Errors and required state belong to the form-field contract, not the checkbox label.",
|
|
422
|
-
],
|
|
423
|
-
testing: [
|
|
424
|
-
"Verify checked, unchecked, and mixed announcements if applicable.",
|
|
425
|
-
"Check Space activation and label click behavior.",
|
|
426
|
-
"Confirm helper and error text are programmatically associated when present.",
|
|
427
|
-
],
|
|
428
|
-
exampleSnippets: {
|
|
429
|
-
html: `<label>\n <input type="checkbox" name="updates" />\n Email me product updates\n</label>`,
|
|
430
|
-
},
|
|
431
|
-
},
|
|
432
|
-
{
|
|
433
|
-
id: "radio-group",
|
|
434
|
-
title: "Radio Group",
|
|
435
|
-
aliases: ["radio", "radio group", "option set", "single choice"],
|
|
436
|
-
url: "https://www.w3.org/WAI/ARIA/apg/patterns/radio/",
|
|
437
|
-
summary: "Radio group represents a single required or optional choice from a fixed set.",
|
|
438
|
-
nativeFirst: [
|
|
439
|
-
"Use native radio inputs grouped by name whenever possible.",
|
|
440
|
-
"Wrap the set in fieldset/legend when that gives you the right semantics.",
|
|
441
|
-
],
|
|
442
|
-
accessibleName: [
|
|
443
|
-
"The group needs a clear label, often from legend or aria-labelledby.",
|
|
444
|
-
"Each radio option should expose the visible option label as its accessible name.",
|
|
445
|
-
],
|
|
446
|
-
roles: ["radiogroup", "radio"],
|
|
447
|
-
requiredStates: ["aria-checked on radios when not using native radio inputs."],
|
|
448
|
-
optionalStates: ["aria-describedby on the group for helper or validation text."],
|
|
449
|
-
forbiddenPatterns: [
|
|
450
|
-
"Do not use checkboxes when only one option may be selected.",
|
|
451
|
-
"Do not leave every radio in the tab order when using custom widgets.",
|
|
452
|
-
],
|
|
453
|
-
keyboard: [
|
|
454
|
-
"Tab enters and exits the group.",
|
|
455
|
-
"Arrow keys move selection within the group in custom radio implementations.",
|
|
456
|
-
"Space checks the focused radio when it is not already selected.",
|
|
457
|
-
],
|
|
458
|
-
focus: [
|
|
459
|
-
"Custom radios commonly use roving tabindex so only one option is tabbable.",
|
|
460
|
-
"Focus should land on the selected radio when entering a preselected group.",
|
|
461
|
-
],
|
|
462
|
-
implementationNotes: [
|
|
463
|
-
"Document default selection behavior and what happens when no option is selected initially.",
|
|
464
|
-
"Keep helper and error messaging associated with the group, not individual decorative wrappers.",
|
|
465
|
-
],
|
|
466
|
-
testing: [
|
|
467
|
-
"Verify group label announcement and arrow-key movement.",
|
|
468
|
-
"Check that only one radio can be selected at a time.",
|
|
469
|
-
"Confirm validation messaging is announced when present.",
|
|
470
|
-
],
|
|
471
|
-
exampleSnippets: {
|
|
472
|
-
html: `<fieldset>\n <legend>Notification frequency</legend>\n <label><input type="radio" name="frequency" value="daily" /> Daily</label>\n</fieldset>`,
|
|
473
|
-
},
|
|
474
|
-
},
|
|
475
|
-
{
|
|
476
|
-
id: "switch",
|
|
477
|
-
title: "Switch",
|
|
478
|
-
aliases: ["switch", "toggle", "toggle switch"],
|
|
479
|
-
url: "https://www.w3.org/WAI/ARIA/apg/patterns/switch/",
|
|
480
|
-
summary: "Switch communicates an on/off setting and should read like a persistent state, not a one-time action.",
|
|
481
|
-
nativeFirst: [
|
|
482
|
-
"Use a native checkbox when the UI does not require explicit switch semantics.",
|
|
483
|
-
"Reserve role=switch for controls that users naturally understand as on/off settings.",
|
|
484
|
-
],
|
|
485
|
-
accessibleName: [
|
|
486
|
-
"Label the setting being controlled, not the current state.",
|
|
487
|
-
"Let checked state communicate on/off rather than adding those words into the accessible name.",
|
|
488
|
-
],
|
|
489
|
-
roles: ["switch"],
|
|
490
|
-
requiredStates: ["aria-checked when not using a native checkbox under the hood."],
|
|
491
|
-
optionalStates: ["aria-describedby for supporting helper or caution text."],
|
|
492
|
-
forbiddenPatterns: [
|
|
493
|
-
"Do not use a switch for immediate actions like Save or Delete.",
|
|
494
|
-
"Do not duplicate on/off inside the accessible name if state already exposes it.",
|
|
495
|
-
],
|
|
496
|
-
keyboard: ["Tab moves focus to the switch.", "Space toggles the switch."],
|
|
497
|
-
focus: [
|
|
498
|
-
"Focus remains on the switch after toggling.",
|
|
499
|
-
"Document the distinction between focus styling and checked styling.",
|
|
500
|
-
],
|
|
501
|
-
implementationNotes: [
|
|
502
|
-
"Switches should feel like persistent settings; if the control triggers navigation or submit behavior, use a different pattern.",
|
|
503
|
-
"Helper or warning text may be important when toggling has wider consequences.",
|
|
504
|
-
],
|
|
505
|
-
testing: [
|
|
506
|
-
"Verify on/off announcements with major screen readers.",
|
|
507
|
-
"Check Space activation and focus visibility.",
|
|
508
|
-
"Confirm helper or caution text is associated when relevant.",
|
|
509
|
-
],
|
|
510
|
-
exampleSnippets: {
|
|
511
|
-
html: `<button type="button" role="switch" aria-checked="false" aria-labelledby="wifi-label"></button>\n<span id="wifi-label">Wi-Fi</span>`,
|
|
512
|
-
},
|
|
513
|
-
},
|
|
514
|
-
{
|
|
515
|
-
id: "slider",
|
|
516
|
-
title: "Slider",
|
|
517
|
-
aliases: ["slider", "range", "seekbar", "progress control", "scrubber"],
|
|
518
|
-
url: "https://www.w3.org/WAI/ARIA/apg/patterns/slider/",
|
|
519
|
-
summary: "Slider lets users set a value within a range and needs an accessible name plus accurate current, min, and max values.",
|
|
520
|
-
nativeFirst: [
|
|
521
|
-
"Use native input type=range whenever the browser control is acceptable.",
|
|
522
|
-
"Reach for custom slider semantics only when the product needs visuals or interaction beyond the native control.",
|
|
523
|
-
],
|
|
524
|
-
accessibleName: [
|
|
525
|
-
"The slider needs a visible label or equivalent accessible name that describes the controlled value.",
|
|
526
|
-
"Use aria-valuetext when the spoken value should be more descriptive than the raw numeric value.",
|
|
527
|
-
],
|
|
528
|
-
roles: ["slider"],
|
|
529
|
-
requiredStates: ["aria-valuemin", "aria-valuemax", "aria-valuenow when not using a native range input"],
|
|
530
|
-
optionalStates: ["aria-orientation when vertical", "aria-valuetext for formatted output such as volume or percentage labels."],
|
|
531
|
-
forbiddenPatterns: [
|
|
532
|
-
"Do not use slider semantics for a one-time action button.",
|
|
533
|
-
"Do not expose stale aria-valuenow or valuetext while the visual thumb moves.",
|
|
534
|
-
],
|
|
535
|
-
keyboard: [
|
|
536
|
-
"Arrow keys adjust the current value.",
|
|
537
|
-
"Home and End typically move to the minimum and maximum values.",
|
|
538
|
-
"Page Up and Page Down may optionally adjust by a larger step.",
|
|
539
|
-
],
|
|
540
|
-
focus: [
|
|
541
|
-
"Focus stays on the thumb while the value changes.",
|
|
542
|
-
"Make sure the focus indicator remains visible at every thumb position.",
|
|
543
|
-
],
|
|
544
|
-
implementationNotes: [
|
|
545
|
-
"Document the step size, min/max range, and whether the value updates continuously or only on commit.",
|
|
546
|
-
"For dual-thumb range sliders, document separate names and collision behavior for each thumb.",
|
|
547
|
-
],
|
|
548
|
-
testing: [
|
|
549
|
-
"Verify keyboard adjustment across the full range.",
|
|
550
|
-
"Check value announcements with screen readers, including formatted aria-valuetext when used.",
|
|
551
|
-
"Confirm touch and pointer interaction do not break keyboard semantics.",
|
|
552
|
-
],
|
|
553
|
-
exampleSnippets: {
|
|
554
|
-
html: `<label for="volume">Volume</label>\n<input id="volume" type="range" min="0" max="100" value="40" />`,
|
|
555
|
-
},
|
|
556
|
-
},
|
|
557
|
-
{
|
|
558
|
-
id: "toolbar",
|
|
559
|
-
title: "Toolbar",
|
|
560
|
-
aliases: ["toolbar", "format bar", "editor controls", "action bar"],
|
|
561
|
-
url: "https://www.w3.org/WAI/ARIA/apg/patterns/toolbar/",
|
|
562
|
-
summary: "Toolbar groups related controls and typically uses one tab stop plus arrow-key movement between internal controls.",
|
|
563
|
-
nativeFirst: [
|
|
564
|
-
"Use ordinary grouped buttons when a toolbar-specific keyboard model is unnecessary.",
|
|
565
|
-
"Use toolbar semantics only when the grouped controls are meant to behave as one composite widget.",
|
|
566
|
-
],
|
|
567
|
-
accessibleName: [
|
|
568
|
-
"The toolbar needs an accessible name when more than one toolbar is present or the purpose is not obvious from surrounding context.",
|
|
569
|
-
"Individual controls inside the toolbar still need their own labels.",
|
|
570
|
-
],
|
|
571
|
-
roles: ["toolbar"],
|
|
572
|
-
requiredStates: ["A labeled container and a documented keyboard model for internal navigation."],
|
|
573
|
-
optionalStates: ["aria-orientation when vertical."],
|
|
574
|
-
forbiddenPatterns: [
|
|
575
|
-
"Do not turn unrelated page actions into a toolbar just for styling.",
|
|
576
|
-
"Do not leave every internal control in the tab order if you document a composite arrow-key model.",
|
|
577
|
-
],
|
|
578
|
-
keyboard: [
|
|
579
|
-
"Tab enters and exits the toolbar.",
|
|
580
|
-
"Arrow keys move focus between supported controls inside the toolbar.",
|
|
581
|
-
"Activation keys remain those of the individual control types.",
|
|
582
|
-
],
|
|
583
|
-
focus: [
|
|
584
|
-
"On entry, focus should land on the last-focused or first enabled control according to the documented behavior.",
|
|
585
|
-
"Disabled controls should not trap focus unexpectedly.",
|
|
586
|
-
],
|
|
587
|
-
implementationNotes: [
|
|
588
|
-
"Mixed control types inside a toolbar still need their own APG behavior, such as button, menu button, or radio group.",
|
|
589
|
-
"Document whether focus memory is preserved when users leave and return to the toolbar.",
|
|
590
|
-
],
|
|
591
|
-
testing: [
|
|
592
|
-
"Verify one clean tab stop for the toolbar if that is the documented behavior.",
|
|
593
|
-
"Check arrow-key navigation and wrapping rules.",
|
|
594
|
-
"Confirm embedded composite controls do not create conflicting keyboard models.",
|
|
595
|
-
],
|
|
596
|
-
exampleSnippets: {
|
|
597
|
-
html: `<div role="toolbar" aria-label="Text formatting">\n <button type="button" aria-pressed="false">Bold</button>\n</div>`,
|
|
598
|
-
},
|
|
599
|
-
},
|
|
600
|
-
{
|
|
601
|
-
id: "grid",
|
|
602
|
-
title: "Grid",
|
|
603
|
-
aliases: ["grid", "data grid", "table grid", "spreadsheet", "calendar grid"],
|
|
604
|
-
url: "https://www.w3.org/WAI/ARIA/apg/patterns/grid/",
|
|
605
|
-
summary: "Grid is a composite widget for two-dimensional navigation and should only be used when interactive cell navigation is required.",
|
|
606
|
-
nativeFirst: [
|
|
607
|
-
"Prefer native table markup for read-only tabular data.",
|
|
608
|
-
"Use grid semantics only when users need active cell-by-cell keyboard navigation or interactive descendants inside cells.",
|
|
609
|
-
],
|
|
610
|
-
accessibleName: [
|
|
611
|
-
"The grid needs an accessible name that describes the dataset or task.",
|
|
612
|
-
"Rows, columns, and interactive cells should expose stable labels so position and meaning are clear.",
|
|
613
|
-
],
|
|
614
|
-
roles: ["grid", "row", "gridcell", "columnheader", "rowheader"],
|
|
615
|
-
requiredStates: ["A documented cell focus model and correct row/cell roles."],
|
|
616
|
-
optionalStates: ["aria-rowcount, aria-colcount, aria-rowindex, and aria-colindex when virtualization or partial rendering requires them."],
|
|
617
|
-
forbiddenPatterns: [
|
|
618
|
-
"Do not use grid just to style a collection into columns.",
|
|
619
|
-
"Do not leave both the grid container and every interactive cell fully tabbable without a clear focus model.",
|
|
620
|
-
],
|
|
621
|
-
keyboard: [
|
|
622
|
-
"Arrow keys move focus by cell.",
|
|
623
|
-
"Home, End, Page Up, and Page Down often have grid-specific navigation behavior.",
|
|
624
|
-
"Enter or F2 may switch between navigation mode and cell interaction mode depending on the design.",
|
|
625
|
-
],
|
|
626
|
-
focus: [
|
|
627
|
-
"Document whether focus sits on the grid container with aria-activedescendant or moves into cells directly.",
|
|
628
|
-
"Virtualized grids need stable focus and announcement behavior while rows mount and unmount.",
|
|
629
|
-
],
|
|
630
|
-
implementationNotes: [
|
|
631
|
-
"Grid is advanced and easy to get wrong; use it only when a table, list, or listbox cannot meet the requirement.",
|
|
632
|
-
"If cells contain buttons, checkboxes, or links, document when arrow keys navigate versus when inner controls take over.",
|
|
633
|
-
],
|
|
634
|
-
testing: [
|
|
635
|
-
"Verify row and column announcements, active cell movement, and virtualization announcements.",
|
|
636
|
-
"Check keyboard entry and exit from interactive cells.",
|
|
637
|
-
"Confirm sorting, selection, and edit states are announced where applicable.",
|
|
638
|
-
],
|
|
639
|
-
exampleSnippets: {
|
|
640
|
-
html: `<div role="grid" aria-label="Orders">\n <div role="row">\n <div role="gridcell" tabindex="0">#1024</div>\n </div>\n</div>`,
|
|
641
|
-
},
|
|
642
|
-
},
|
|
643
|
-
{
|
|
644
|
-
id: "treeview",
|
|
645
|
-
title: "Treeview",
|
|
646
|
-
aliases: ["tree", "treeview", "file tree", "navigation tree", "nested list"],
|
|
647
|
-
url: "https://www.w3.org/WAI/ARIA/apg/patterns/treeview/",
|
|
648
|
-
summary: "Treeview presents a hierarchical collection with expand/collapse behavior and structured keyboard navigation.",
|
|
649
|
-
nativeFirst: [
|
|
650
|
-
"Use nested lists and disclosure buttons when users do not need a full treeview interaction model.",
|
|
651
|
-
"Reserve treeview for true hierarchical navigation that benefits from arrow-key traversal and selection state.",
|
|
652
|
-
],
|
|
653
|
-
accessibleName: [
|
|
654
|
-
"The tree needs a clear label describing the hierarchy being browsed.",
|
|
655
|
-
"Each treeitem should expose a concise visible label; expanded state should come from aria-expanded, not label text.",
|
|
656
|
-
],
|
|
657
|
-
roles: ["tree", "treeitem", "group"],
|
|
658
|
-
requiredStates: ["aria-expanded on parent treeitems that can open or close children."],
|
|
659
|
-
optionalStates: ["aria-selected when the tree includes selection semantics.", "aria-level, aria-posinset, and aria-setsize when DOM structure does not express hierarchy clearly."],
|
|
660
|
-
forbiddenPatterns: [
|
|
661
|
-
"Do not use treeview for a flat list of links.",
|
|
662
|
-
"Do not duplicate expanded/collapsed wording in the accessible name of every item.",
|
|
663
|
-
],
|
|
664
|
-
keyboard: [
|
|
665
|
-
"Arrow Right expands a closed parent or moves to its first child.",
|
|
666
|
-
"Arrow Left collapses an open parent or moves to its parent.",
|
|
667
|
-
"Arrow Up and Arrow Down move between visible treeitems.",
|
|
668
|
-
"Home and End move to the first and last visible items.",
|
|
669
|
-
],
|
|
670
|
-
focus: [
|
|
671
|
-
"Only one treeitem should usually be tabbable at a time.",
|
|
672
|
-
"Selection and focus may be separate; document whether moving focus also changes selection.",
|
|
673
|
-
],
|
|
674
|
-
implementationNotes: [
|
|
675
|
-
"Document whether the tree is navigation-only, selection-based, or both.",
|
|
676
|
-
"Async loading and lazy expansion need announcements so new children are understandable.",
|
|
677
|
-
],
|
|
678
|
-
testing: [
|
|
679
|
-
"Verify parent/child navigation and expansion behavior with keyboard only.",
|
|
680
|
-
"Check that expanded state and item position are announced correctly.",
|
|
681
|
-
"Confirm selection behavior matches the documented contract.",
|
|
682
|
-
],
|
|
683
|
-
exampleSnippets: {
|
|
684
|
-
html: `<ul role="tree" aria-label="Project files">\n <li role="treeitem" aria-expanded="false" tabindex="0">src</li>\n</ul>`,
|
|
685
|
-
},
|
|
686
|
-
},
|
|
687
|
-
];
|
|
688
|
-
|
|
689
|
-
function normalize(value: string): string {
|
|
690
|
-
return value.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim();
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
function collectSnapshotTerms(snapshot: NodeSnapshot): string[] {
|
|
694
|
-
return [
|
|
695
|
-
snapshot.name,
|
|
696
|
-
snapshot.description,
|
|
697
|
-
snapshot.type,
|
|
698
|
-
...snapshot.childNames,
|
|
699
|
-
...snapshot.textLayers.map((layer) => layer.name),
|
|
700
|
-
...snapshot.textLayers.map((layer) => layer.characters),
|
|
701
|
-
...snapshot.componentProperties.map((prop) => prop.name),
|
|
702
|
-
...snapshot.componentProperties.flatMap((prop) => prop.options),
|
|
703
|
-
...Object.keys(snapshot.variantProperties || {}),
|
|
704
|
-
...Object.values(snapshot.variantProperties || {}),
|
|
705
|
-
...Object.keys(snapshot.variantGroupProperties || {}),
|
|
706
|
-
...Object.values(snapshot.variantGroupProperties || {}).flat(),
|
|
707
|
-
]
|
|
708
|
-
.map(normalize)
|
|
709
|
-
.filter(Boolean);
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
export function guessPattern(snapshot: NodeSnapshot, hint?: string): { definition: ApgPatternDefinition; confidence: number; matchedBy: string[] } {
|
|
713
|
-
const terms = collectSnapshotTerms(snapshot);
|
|
714
|
-
const haystack = new Set(terms);
|
|
715
|
-
const hintNormalized = hint ? normalize(hint) : "";
|
|
716
|
-
let best = APG_PATTERNS[0];
|
|
717
|
-
let bestScore = -1;
|
|
718
|
-
let bestMatches: string[] = [];
|
|
719
|
-
|
|
720
|
-
for (const pattern of APG_PATTERNS) {
|
|
721
|
-
const matches = new Set<string>();
|
|
722
|
-
let score = 0;
|
|
723
|
-
|
|
724
|
-
for (const alias of pattern.aliases) {
|
|
725
|
-
const normalizedAlias = normalize(alias);
|
|
726
|
-
if (hintNormalized && (hintNormalized.includes(normalizedAlias) || normalizedAlias.includes(hintNormalized))) {
|
|
727
|
-
score += 5;
|
|
728
|
-
matches.add(`hint:${alias}`);
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
for (const term of haystack) {
|
|
732
|
-
if (term.includes(normalizedAlias) || normalizedAlias.includes(term)) {
|
|
733
|
-
score += normalizedAlias === term ? 4 : 2;
|
|
734
|
-
matches.add(`node:${alias}`);
|
|
735
|
-
}
|
|
736
|
-
}
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
if (pattern.id === "dialog-modal" && snapshot.height >= 240 && /modal|dialog|sheet|drawer/.test(normalize(snapshot.name))) {
|
|
740
|
-
score += 3;
|
|
741
|
-
matches.add("heuristic:overlay-shell");
|
|
742
|
-
}
|
|
743
|
-
if (pattern.id === "tabs" && Object.keys(snapshot.variantGroupProperties).some((key) => /tab|selected|active/.test(normalize(key)))) {
|
|
744
|
-
score += 2;
|
|
745
|
-
matches.add("heuristic:tab-variants");
|
|
746
|
-
}
|
|
747
|
-
if (pattern.id === "button" && /button|cta|action/.test(normalize(snapshot.name))) {
|
|
748
|
-
score += 2;
|
|
749
|
-
matches.add("heuristic:button-name");
|
|
750
|
-
}
|
|
751
|
-
if (pattern.id === "switch" && /\b(on|off)\b/.test(terms.join(" "))) {
|
|
752
|
-
score += 1;
|
|
753
|
-
matches.add("heuristic:on-off-copy");
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
if (score > bestScore) {
|
|
757
|
-
best = pattern;
|
|
758
|
-
bestScore = score;
|
|
759
|
-
bestMatches = Array.from(matches);
|
|
760
|
-
}
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
const confidence = Math.max(0.34, Math.min(0.96, 0.34 + bestScore * 0.08));
|
|
764
|
-
return { definition: best, confidence, matchedBy: bestMatches.length > 0 ? bestMatches : ["fallback:best-name-match"] };
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
function summarizeTarget(snapshot: NodeSnapshot): string {
|
|
768
|
-
const size = `${Math.round(snapshot.width)} x ${Math.round(snapshot.height)} px`;
|
|
769
|
-
const variants = Object.keys(snapshot.variantGroupProperties).length > 0
|
|
770
|
-
? `Variants: ${Object.keys(snapshot.variantGroupProperties).join(", ")}.`
|
|
771
|
-
: "No explicit variant groups detected.";
|
|
772
|
-
|
|
773
|
-
return `${snapshot.name} is a ${snapshot.type.toLowerCase()} target sized ${size}. ${variants}`;
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
function buildImplementationSnippet(
|
|
777
|
-
pattern: ApgPatternDefinition,
|
|
778
|
-
framework: NonNullable<FigmaApgDocArgs["framework"]>,
|
|
779
|
-
includeCodeExamples: boolean
|
|
780
|
-
): string | undefined {
|
|
781
|
-
if (!includeCodeExamples) return undefined;
|
|
782
|
-
return pattern.exampleSnippets[framework] || pattern.exampleSnippets.html;
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
function inferVariantA11yRules(snapshot: NodeSnapshot, pattern: ApgPatternDefinition): string[] {
|
|
786
|
-
const rules: string[] = [];
|
|
787
|
-
const groups = Object.entries(snapshot.variantGroupProperties);
|
|
788
|
-
const allEntries = [
|
|
789
|
-
...groups.map(([name, values]) => ({ name, values })),
|
|
790
|
-
...snapshot.componentProperties
|
|
791
|
-
.filter((prop) => prop.options.length > 0)
|
|
792
|
-
.map((prop) => ({ name: prop.name, values: prop.options })),
|
|
793
|
-
];
|
|
794
|
-
|
|
795
|
-
for (const entry of allEntries) {
|
|
796
|
-
const key = normalize(entry.name);
|
|
797
|
-
const values = entry.values.map((value) => normalize(value));
|
|
798
|
-
|
|
799
|
-
if (/state|status/.test(key)) {
|
|
800
|
-
if (values.some((value) => /disabled/.test(value))) {
|
|
801
|
-
rules.push("Disabled variants must remove or suppress activation while preserving a clear disabled semantic state.");
|
|
802
|
-
}
|
|
803
|
-
if (values.some((value) => /focus|focused/.test(value))) {
|
|
804
|
-
rules.push("Focus variants are documentation artifacts only; the rendered component still needs a real visible focus indicator triggered by keyboard focus.");
|
|
805
|
-
}
|
|
806
|
-
if (values.some((value) => /error|invalid/.test(value))) {
|
|
807
|
-
rules.push("Error variants should pair visual styling with programmatic invalid state, descriptive error text, and helper/error association.");
|
|
808
|
-
}
|
|
809
|
-
if (values.some((value) => /loading|progress/.test(value))) {
|
|
810
|
-
rules.push("Loading variants should preserve an accessible name and announce busy/progress state when user feedback changes materially.");
|
|
811
|
-
}
|
|
812
|
-
if (values.some((value) => /selected|active|checked|on/.test(value))) {
|
|
813
|
-
rules.push(`Selected or active variants should map to the ${pattern.optionalStates[0] || "documented state"} only when the APG pattern actually supports it.`);
|
|
814
|
-
}
|
|
815
|
-
}
|
|
816
|
-
|
|
817
|
-
if (/size/.test(key)) {
|
|
818
|
-
rules.push("Size variants must keep touch target, focus ring visibility, and label readability intact at every size.");
|
|
819
|
-
}
|
|
820
|
-
|
|
821
|
-
if (/icon/.test(key) || values.some((value) => /icon only|icon/.test(value))) {
|
|
822
|
-
rules.push("Icon-only variants need an explicit accessible name because the visual label may be absent.");
|
|
823
|
-
}
|
|
824
|
-
|
|
825
|
-
if (/theme|tone|intent|kind|priority|appearance/.test(key)) {
|
|
826
|
-
rules.push("Visual tone variants must not be the only channel for meaning; preserve semantics and contrast across every appearance.");
|
|
827
|
-
}
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
return Array.from(new Set(rules)).slice(0, 8);
|
|
831
|
-
}
|
|
832
|
-
|
|
833
|
-
async function writeDescriptionToNode(nodeId: string, document: GeneratedDocument, mode: NonNullable<FigmaApgDocArgs["descriptionMode"]>): Promise<void> {
|
|
834
|
-
const bridge = await getBridge();
|
|
835
|
-
const report = formatDocumentReport(document);
|
|
836
|
-
const result = await bridge.execute(`
|
|
837
|
-
(async () => {
|
|
838
|
-
await figma.loadAllPagesAsync();
|
|
839
|
-
const node = await figma.getNodeByIdAsync(${JSON.stringify(nodeId)});
|
|
840
|
-
if (!node) throw new Error("Node not found");
|
|
841
|
-
const existing = "description" in node && typeof node.description === "string" ? node.description : "";
|
|
842
|
-
const nextDescription = ${JSON.stringify(mode)} === "append" && existing.trim().length > 0
|
|
843
|
-
? existing + "\\n\\n---\\n\\n" + ${JSON.stringify(report)}
|
|
844
|
-
: ${JSON.stringify(report)};
|
|
845
|
-
if (!("description" in node)) {
|
|
846
|
-
throw new Error("Target node does not support description updates.");
|
|
847
|
-
}
|
|
848
|
-
node.description = nextDescription;
|
|
849
|
-
return { nodeId: node.id, descriptionLength: nextDescription.length };
|
|
850
|
-
})();
|
|
851
|
-
`);
|
|
852
|
-
|
|
853
|
-
if (!result.success) {
|
|
854
|
-
throw new Error(`figma_apg_doc: failed to update node description: ${result.error}`);
|
|
855
|
-
}
|
|
856
|
-
}
|
|
857
|
-
|
|
858
|
-
export function shouldFallbackToGeneratedPage(error: unknown): boolean {
|
|
859
|
-
const message = error instanceof Error ? error.message : String(error || "");
|
|
860
|
-
const normalized = message.toLowerCase();
|
|
861
|
-
return normalized.includes("failed to update node description")
|
|
862
|
-
|| normalized.includes("does not support description updates")
|
|
863
|
-
|| (normalized.includes("plugin") && normalized.includes("write"))
|
|
864
|
-
|| (normalized.includes("internal") && normalized.includes("write"));
|
|
865
|
-
}
|
|
866
|
-
|
|
867
|
-
function buildApgDocument(
|
|
868
|
-
snapshot: NodeSnapshot,
|
|
869
|
-
match: { definition: ApgPatternDefinition; confidence: number; matchedBy: string[] },
|
|
870
|
-
snippet?: string
|
|
871
|
-
): GeneratedDocument {
|
|
872
|
-
const pattern = match.definition;
|
|
873
|
-
const variantSummary = Object.entries(snapshot.variantGroupProperties)
|
|
874
|
-
.map(([key, values]) => `${key}: ${values.join(", ")}`)
|
|
875
|
-
.slice(0, 5);
|
|
876
|
-
const namingHints = snapshot.textLayers
|
|
877
|
-
.map((layer) => layer.characters.trim())
|
|
878
|
-
.filter(Boolean)
|
|
879
|
-
.slice(0, 3)
|
|
880
|
-
.map((text) => `Visible copy detected: "${text.length > 70 ? `${text.slice(0, 69)}...` : text}"`);
|
|
881
|
-
const variantA11yRules = inferVariantA11yRules(snapshot, pattern);
|
|
882
|
-
|
|
883
|
-
const sections: GeneratedDocument["sections"] = [
|
|
884
|
-
{
|
|
885
|
-
title: "APG pattern mapping",
|
|
886
|
-
style: "bullets" as const,
|
|
887
|
-
items: [
|
|
888
|
-
`Matched pattern: ${pattern.title}`,
|
|
889
|
-
`Source: ${pattern.url}`,
|
|
890
|
-
`Confidence: ${Math.round(match.confidence * 100)}%`,
|
|
891
|
-
`Matched by: ${match.matchedBy.join(", ")}`,
|
|
892
|
-
],
|
|
893
|
-
},
|
|
894
|
-
{
|
|
895
|
-
title: "Target summary",
|
|
896
|
-
style: "bullets" as const,
|
|
897
|
-
items: [
|
|
898
|
-
summarizeTarget(snapshot),
|
|
899
|
-
snapshot.description ? `Figma description: ${snapshot.description}` : "Figma description is missing or too short; add usage context for stronger docs.",
|
|
900
|
-
...(variantSummary.length > 0 ? variantSummary : ["No explicit variant groups found in this node."]),
|
|
901
|
-
],
|
|
902
|
-
},
|
|
903
|
-
{
|
|
904
|
-
title: "Native-first recommendation",
|
|
905
|
-
style: "checklist" as const,
|
|
906
|
-
items: pattern.nativeFirst,
|
|
907
|
-
},
|
|
908
|
-
{
|
|
909
|
-
title: "Accessible name and descriptions",
|
|
910
|
-
style: "bullets" as const,
|
|
911
|
-
items: pattern.accessibleName.concat(
|
|
912
|
-
namingHints.length > 0 ? namingHints : ["No visible text was detected. Icon-only or abstract variants need an explicit accessible naming rule."]
|
|
913
|
-
),
|
|
914
|
-
},
|
|
915
|
-
{
|
|
916
|
-
title: "Roles, states, and properties",
|
|
917
|
-
style: "bullets" as const,
|
|
918
|
-
items: [
|
|
919
|
-
`Roles: ${pattern.roles.join(", ")}`,
|
|
920
|
-
`Required states/properties: ${pattern.requiredStates.join(" | ")}`,
|
|
921
|
-
`Optional states/properties: ${pattern.optionalStates.join(" | ")}`,
|
|
922
|
-
],
|
|
923
|
-
},
|
|
924
|
-
{
|
|
925
|
-
title: "Variant-specific accessibility rules",
|
|
926
|
-
style: "bullets" as const,
|
|
927
|
-
items: variantA11yRules.length > 0
|
|
928
|
-
? variantA11yRules
|
|
929
|
-
: ["No stateful variant metadata was detected; document disabled, error, selected, loading, and icon-only behavior explicitly if the code component supports them."],
|
|
930
|
-
},
|
|
931
|
-
{
|
|
932
|
-
title: "Keyboard and focus contract",
|
|
933
|
-
style: "checklist" as const,
|
|
934
|
-
items: pattern.keyboard.concat(pattern.focus),
|
|
935
|
-
},
|
|
936
|
-
{
|
|
937
|
-
title: "Authoring rules and anti-patterns",
|
|
938
|
-
style: "bullets" as const,
|
|
939
|
-
items: pattern.implementationNotes.concat(pattern.forbiddenPatterns.map((item) => `Avoid: ${item}`)),
|
|
940
|
-
},
|
|
941
|
-
{
|
|
942
|
-
title: "QA checklist",
|
|
943
|
-
style: "checklist" as const,
|
|
944
|
-
items: pattern.testing,
|
|
945
|
-
},
|
|
946
|
-
{
|
|
947
|
-
title: "References",
|
|
948
|
-
style: "bullets" as const,
|
|
949
|
-
items: [pattern.url].concat(GENERAL_REFERENCES),
|
|
950
|
-
},
|
|
951
|
-
];
|
|
952
|
-
|
|
953
|
-
if (snippet) {
|
|
954
|
-
sections.push({
|
|
955
|
-
title: "Implementation starter",
|
|
956
|
-
style: "paragraph" as const,
|
|
957
|
-
items: [snippet],
|
|
958
|
-
});
|
|
959
|
-
}
|
|
960
|
-
|
|
961
|
-
return {
|
|
962
|
-
type: "accessibility",
|
|
963
|
-
title: `${snapshot.name} APG Accessibility Spec`,
|
|
964
|
-
summary: `${pattern.summary} This documentation is grounded in the WAI-ARIA APG pattern and tailored to the inspected Figma component.`,
|
|
965
|
-
sections,
|
|
966
|
-
};
|
|
967
|
-
}
|
|
968
|
-
|
|
969
|
-
export async function figmaApgDocHandler(args: FigmaApgDocArgs): Promise<FigmaApgDocResult> {
|
|
970
|
-
const framework = args.framework || "html";
|
|
971
|
-
const nodeId = await resolveTargetNodeId({ nodeId: args.nodeId });
|
|
972
|
-
const snapshot = await captureSnapshot(nodeId);
|
|
973
|
-
const match = guessPattern(snapshot, args.patternHint);
|
|
974
|
-
const snippet = buildImplementationSnippet(match.definition, framework, args.includeCodeExamples ?? true);
|
|
975
|
-
const document = buildApgDocument(snapshot, match, snippet);
|
|
976
|
-
const report = formatDocumentReport(document);
|
|
977
|
-
const warnings: string[] = [];
|
|
978
|
-
|
|
979
|
-
if (match.confidence < 0.55) {
|
|
980
|
-
warnings.push(`APG pattern match confidence is only ${Math.round(match.confidence * 100)}%. Review the mapped pattern before treating this document as canonical.`);
|
|
981
|
-
}
|
|
982
|
-
|
|
983
|
-
let figmaPageId: string | undefined;
|
|
984
|
-
if (args.outputFormat === "figma-page" || args.outputFormat === "all") {
|
|
985
|
-
figmaPageId = await createDocumentationPages([document], args.pageName || `APG Doc - ${snapshot.name}`);
|
|
986
|
-
}
|
|
987
|
-
let descriptionUpdated = false;
|
|
988
|
-
if (args.writeToDescription) {
|
|
989
|
-
try {
|
|
990
|
-
await writeDescriptionToNode(snapshot.id, document, args.descriptionMode || "replace");
|
|
991
|
-
descriptionUpdated = true;
|
|
992
|
-
} catch (error) {
|
|
993
|
-
const canFallbackToPage = args.outputFormat === "figma-page" || args.outputFormat === "all";
|
|
994
|
-
if (!figmaPageId && canFallbackToPage) {
|
|
995
|
-
figmaPageId = await createDocumentationPages([document], args.pageName || `APG Doc - ${snapshot.name}`);
|
|
996
|
-
}
|
|
997
|
-
|
|
998
|
-
if (figmaPageId && shouldFallbackToGeneratedPage(error)) {
|
|
999
|
-
warnings.push("Skipped writing to the node description after a Figma plugin write failure; generated a standalone accessibility doc page instead.");
|
|
1000
|
-
} else {
|
|
1001
|
-
throw error;
|
|
1002
|
-
}
|
|
1003
|
-
}
|
|
1004
|
-
}
|
|
1005
|
-
|
|
1006
|
-
const logEntry = await decisionLog.log({
|
|
1007
|
-
tool: "figma_apg_doc",
|
|
1008
|
-
nodeIds: figmaPageId ? [snapshot.id, figmaPageId] : [snapshot.id],
|
|
1009
|
-
rationale: `Generated APG-backed documentation for ${snapshot.name} using ${match.definition.title} guidance at ${Math.round(match.confidence * 100)}% confidence.`,
|
|
1010
|
-
reversible: true,
|
|
1011
|
-
metadata: {
|
|
1012
|
-
outputFormat: args.outputFormat,
|
|
1013
|
-
patternId: match.definition.id,
|
|
1014
|
-
confidence: match.confidence,
|
|
1015
|
-
framework,
|
|
1016
|
-
includeCodeExamples: args.includeCodeExamples ?? true,
|
|
1017
|
-
writeToDescription: args.writeToDescription ?? false,
|
|
1018
|
-
descriptionMode: args.descriptionMode || "replace",
|
|
1019
|
-
},
|
|
1020
|
-
});
|
|
1021
|
-
|
|
1022
|
-
return {
|
|
1023
|
-
pattern: {
|
|
1024
|
-
id: match.definition.id,
|
|
1025
|
-
title: match.definition.title,
|
|
1026
|
-
url: match.definition.url,
|
|
1027
|
-
confidence: match.confidence,
|
|
1028
|
-
matchedBy: match.matchedBy,
|
|
1029
|
-
},
|
|
1030
|
-
target: {
|
|
1031
|
-
nodeId: snapshot.id,
|
|
1032
|
-
name: snapshot.name,
|
|
1033
|
-
type: snapshot.type,
|
|
1034
|
-
},
|
|
1035
|
-
report: args.outputFormat === "json" || args.outputFormat === "figma-page" ? undefined : report,
|
|
1036
|
-
figmaPageId,
|
|
1037
|
-
descriptionUpdated,
|
|
1038
|
-
warnings: warnings.length > 0 ? warnings : undefined,
|
|
1039
|
-
document,
|
|
1040
|
-
implementationSnippet: snippet,
|
|
1041
|
-
logEntryId: logEntry.id,
|
|
1042
|
-
};
|
|
1043
|
-
}
|