@monoharada/wcf-mcp 0.5.0 → 0.7.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 +32 -2
- package/core.mjs +428 -24
- package/data/component-selector-guide.json +102 -0
- package/data/design-tokens.json +1 -1
- package/data/guidelines-index.json +1 -1
- package/data/pattern-registry.json +155 -36
- package/package.json +1 -1
- package/server.mjs +1 -0
- package/validator.mjs +88 -26
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"categories": [
|
|
4
|
+
{
|
|
5
|
+
"key": "Form",
|
|
6
|
+
"label": "フォーム入力",
|
|
7
|
+
"description": "User input, selection, and form control components",
|
|
8
|
+
"components": [
|
|
9
|
+
{ "id": "input-text", "tagName": "dads-input-text", "useCase": "Single-line text input (name, email, phone, etc.)" },
|
|
10
|
+
{ "id": "textarea", "tagName": "dads-textarea", "useCase": "Multi-line text input (comments, descriptions, notes)" },
|
|
11
|
+
{ "id": "select", "tagName": "dads-select", "useCase": "Dropdown selection from predefined options" },
|
|
12
|
+
{ "id": "checkbox", "tagName": "dads-checkbox", "useCase": "Boolean toggle or multi-select checkboxes" },
|
|
13
|
+
{ "id": "radio", "tagName": "dads-radio", "useCase": "Single selection from mutually exclusive options" },
|
|
14
|
+
{ "id": "switch", "tagName": "dads-switch", "useCase": "On/off toggle for settings or preferences" },
|
|
15
|
+
{ "id": "combobox", "tagName": "dads-combobox", "useCase": "Text input with autocomplete/suggestion dropdown" },
|
|
16
|
+
{ "id": "date-picker", "tagName": "dads-date-picker", "useCase": "Date selection with calendar interface" },
|
|
17
|
+
{ "id": "file-upload", "tagName": "dads-file-upload", "useCase": "File attachment and upload" },
|
|
18
|
+
{ "id": "fieldset", "tagName": "dads-fieldset", "useCase": "Group related form fields with legend" },
|
|
19
|
+
{ "id": "search-box", "tagName": "dads-search-box", "useCase": "Search input with clear/submit actions" },
|
|
20
|
+
{ "id": "calendar", "tagName": "dads-calendar", "useCase": "Calendar date picker component" }
|
|
21
|
+
]
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"key": "Actions",
|
|
25
|
+
"label": "アクション",
|
|
26
|
+
"description": "Interactive elements that trigger actions or reveal content",
|
|
27
|
+
"components": [
|
|
28
|
+
{ "id": "button", "tagName": "dads-button", "useCase": "Primary action trigger (submit, save, cancel, delete)" },
|
|
29
|
+
{ "id": "dialog", "tagName": "dads-dialog", "useCase": "Modal dialog for confirmations, alerts, or focused tasks" },
|
|
30
|
+
{ "id": "drawer", "tagName": "dads-drawer", "useCase": "Slide-out panel for secondary content or navigation" },
|
|
31
|
+
{ "id": "disclosure", "tagName": "dads-disclosure", "useCase": "Expandable content section (show/hide)" },
|
|
32
|
+
{ "id": "accordion-details", "tagName": "dads-accordion-details", "useCase": "Accordion container for collapsible FAQ or grouped content" },
|
|
33
|
+
{ "id": "accordion-item-details", "tagName": "dads-accordion-item-details", "useCase": "Individual accordion item within an accordion group" }
|
|
34
|
+
]
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"key": "Navigation",
|
|
38
|
+
"label": "ナビゲーション",
|
|
39
|
+
"description": "Page navigation, breadcrumbs, tabs, and menu components",
|
|
40
|
+
"components": [
|
|
41
|
+
{ "id": "breadcrumb", "tagName": "dads-breadcrumb", "useCase": "Hierarchical page location indicator" },
|
|
42
|
+
{ "id": "page-navigation", "tagName": "dads-page-navigation", "useCase": "Pagination for multi-page content (tables, search results)" },
|
|
43
|
+
{ "id": "step-navigation", "tagName": "dads-step-navigation", "useCase": "Step-by-step wizard progress indicator" },
|
|
44
|
+
{ "id": "menu-list", "tagName": "dads-menu-list", "useCase": "Vertical navigation menu list" },
|
|
45
|
+
{ "id": "tab", "tagName": "dads-tab", "useCase": "Tab interface for switching between related content panels" },
|
|
46
|
+
{ "id": "global-menu", "tagName": "dads-global-menu", "useCase": "Site-wide header navigation menu" },
|
|
47
|
+
{ "id": "language-selector", "tagName": "dads-language-selector", "useCase": "Language/locale switcher" },
|
|
48
|
+
{ "id": "hamburger-menu-button", "tagName": "dads-hamburger-menu-button", "useCase": "Mobile menu toggle button" },
|
|
49
|
+
{ "id": "utility-link", "tagName": "dads-utility-link", "useCase": "Utility navigation link (login, settings, help)" },
|
|
50
|
+
{ "id": "mobile-menu", "tagName": "dads-mobile-menu", "useCase": "Mobile-optimized navigation menu" }
|
|
51
|
+
]
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"key": "Content",
|
|
55
|
+
"label": "コンテンツ表示",
|
|
56
|
+
"description": "Text, list, table, and structured content display",
|
|
57
|
+
"components": [
|
|
58
|
+
{ "id": "card", "tagName": "dads-card", "useCase": "Contained content block (article preview, info card)" },
|
|
59
|
+
{ "id": "heading", "tagName": "dads-heading", "useCase": "Section heading with proper hierarchy (h1-h6)" },
|
|
60
|
+
{ "id": "text", "tagName": "dads-text", "useCase": "Styled text block with typography tokens" },
|
|
61
|
+
{ "id": "blockquote", "tagName": "dads-blockquote", "useCase": "Quoted text or citation block" },
|
|
62
|
+
{ "id": "code-block", "tagName": "dads-code-block", "useCase": "Code snippet display with syntax styling" },
|
|
63
|
+
{ "id": "divider", "tagName": "dads-divider", "useCase": "Visual separator between content sections" },
|
|
64
|
+
{ "id": "list", "tagName": "dads-list", "useCase": "Ordered or unordered list" },
|
|
65
|
+
{ "id": "description-list", "tagName": "dads-description-list", "useCase": "Key-value pair list (terms and definitions)" },
|
|
66
|
+
{ "id": "resource-list", "tagName": "dads-resource-list", "useCase": "List of downloadable resources or links" },
|
|
67
|
+
{ "id": "table", "tagName": "dads-table", "useCase": "Data table with sortable columns" },
|
|
68
|
+
{ "id": "table-control", "tagName": "dads-table-control", "useCase": "Table toolbar with filters and actions" }
|
|
69
|
+
]
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"key": "Display",
|
|
73
|
+
"label": "表示・フィードバック",
|
|
74
|
+
"description": "Visual indicators, status displays, and feedback elements",
|
|
75
|
+
"components": [
|
|
76
|
+
{ "id": "avatar", "tagName": "dads-avatar", "useCase": "User profile image or initials" },
|
|
77
|
+
{ "id": "icon", "tagName": "dads-icon", "useCase": "SVG icon display" },
|
|
78
|
+
{ "id": "chip-label", "tagName": "dads-chip-label", "useCase": "Label chip for categories or tags" },
|
|
79
|
+
{ "id": "chip-tag", "tagName": "dads-chip-tag", "useCase": "Interactive tag chip (filterable, removable)" },
|
|
80
|
+
{ "id": "notification-banner", "tagName": "dads-notification-banner", "useCase": "Page-level notification or info banner" },
|
|
81
|
+
{ "id": "emergency-banner", "tagName": "dads-emergency-banner", "useCase": "Critical alert banner (system-wide)" },
|
|
82
|
+
{ "id": "carousel", "tagName": "dads-carousel", "useCase": "Image or content carousel/slider" },
|
|
83
|
+
{ "id": "device-mock", "tagName": "dads-device-mock", "useCase": "Device frame mockup for previews" },
|
|
84
|
+
{ "id": "progress-indicator", "tagName": "dads-progress-indicator", "useCase": "Progress display (determinate or indeterminate)" },
|
|
85
|
+
{ "id": "spinner", "tagName": "dads-spinner", "useCase": "Loading spinner for async operations" },
|
|
86
|
+
{ "id": "progress-bar", "tagName": "dads-progress-bar", "useCase": "Linear progress bar" },
|
|
87
|
+
{ "id": "loading-icon", "tagName": "dads-loading-icon", "useCase": "Inline loading indicator" }
|
|
88
|
+
]
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"key": "Layout",
|
|
92
|
+
"label": "レイアウト",
|
|
93
|
+
"description": "Page layout containers and structural components",
|
|
94
|
+
"components": [
|
|
95
|
+
{ "id": "layout-shell", "tagName": "dads-layout-shell", "useCase": "Page-level layout container (website, app-shell, master-detail)" },
|
|
96
|
+
{ "id": "layout-sidebar", "tagName": "dads-layout-sidebar", "useCase": "Sidebar navigation panel for app layouts" },
|
|
97
|
+
{ "id": "layout-aside", "tagName": "dads-layout-aside", "useCase": "Aside panel for detail/supplementary content" },
|
|
98
|
+
{ "id": "header-container", "tagName": "dads-header-container", "useCase": "Header area container with slots" }
|
|
99
|
+
]
|
|
100
|
+
}
|
|
101
|
+
]
|
|
102
|
+
}
|
package/data/design-tokens.json
CHANGED
|
@@ -6,121 +6,240 @@
|
|
|
6
6
|
"id": "search-form",
|
|
7
7
|
"title": "検索フォーム(最小)",
|
|
8
8
|
"description": "見出し + 検索フォーム(検索語 + ボタン)",
|
|
9
|
-
"requires": [
|
|
9
|
+
"requires": [
|
|
10
|
+
"heading",
|
|
11
|
+
"search-box",
|
|
12
|
+
"button"
|
|
13
|
+
],
|
|
10
14
|
"stability": "stable",
|
|
11
15
|
"contractVersion": "1.0",
|
|
12
|
-
"entryHints": [
|
|
13
|
-
|
|
16
|
+
"entryHints": [
|
|
17
|
+
"boot",
|
|
18
|
+
"@wcf",
|
|
19
|
+
"index"
|
|
20
|
+
],
|
|
21
|
+
"html": "<main data-dads-typeset>\n <dads-heading level=\"1\">検索</dads-heading>\n <form id=\"search-form\">\n <dads-search-box aria-label=\"検索\"></dads-search-box>\n <dads-button type=\"submit\">検索</dads-button>\n </form>\n</main>\n",
|
|
22
|
+
"behavior": "document.querySelector(\"#search-form\").addEventListener(\"submit\", (e) => {\n e.preventDefault();\n const query = e.target.querySelector(\"dads-search-box\").value;\n console.log(\"Search:\", query);\n});"
|
|
14
23
|
},
|
|
15
24
|
"search-results": {
|
|
16
25
|
"id": "search-results",
|
|
17
26
|
"title": "検索結果一覧",
|
|
18
27
|
"description": "見出し + 検索フォーム + 結果カード + ページネーション",
|
|
19
|
-
"requires": [
|
|
28
|
+
"requires": [
|
|
29
|
+
"heading",
|
|
30
|
+
"search-box",
|
|
31
|
+
"card",
|
|
32
|
+
"page-navigation"
|
|
33
|
+
],
|
|
20
34
|
"stability": "stable",
|
|
21
35
|
"contractVersion": "1.0",
|
|
22
|
-
"entryHints": [
|
|
23
|
-
|
|
36
|
+
"entryHints": [
|
|
37
|
+
"boot",
|
|
38
|
+
"@wcf",
|
|
39
|
+
"index"
|
|
40
|
+
],
|
|
41
|
+
"html": "<main data-dads-typeset>\n <dads-heading level=\"1\">検索</dads-heading>\n <form id=\"search-form\">\n <dads-search-box aria-label=\"検索\"></dads-search-box>\n </form>\n <h2>結果</h2>\n <ul>\n <li><dads-card>ダミー結果 1</dads-card></li>\n <li><dads-card>ダミー結果 2</dads-card></li>\n <li><dads-card>ダミー結果 3</dads-card></li>\n </ul>\n <dads-page-navigation current=\"1\" total=\"1\"></dads-page-navigation>\n</main>\n",
|
|
42
|
+
"behavior": "document.querySelector(\"#search-form\").addEventListener(\"submit\", (e) => {\n e.preventDefault();\n const query = e.target.querySelector(\"dads-search-box\").value;\n // Fetch results and update the list\n console.log(\"Search:\", query);\n});"
|
|
24
43
|
},
|
|
25
44
|
"table-with-pagination": {
|
|
26
45
|
"id": "table-with-pagination",
|
|
27
46
|
"title": "テーブル + ページネーション",
|
|
28
47
|
"description": "テーブル一覧とページネーションの基本構成",
|
|
29
|
-
"requires": [
|
|
48
|
+
"requires": [
|
|
49
|
+
"heading",
|
|
50
|
+
"table",
|
|
51
|
+
"page-navigation"
|
|
52
|
+
],
|
|
30
53
|
"stability": "stable",
|
|
31
54
|
"contractVersion": "1.0",
|
|
32
|
-
"entryHints": [
|
|
33
|
-
|
|
55
|
+
"entryHints": [
|
|
56
|
+
"boot",
|
|
57
|
+
"@wcf",
|
|
58
|
+
"index"
|
|
59
|
+
],
|
|
60
|
+
"html": "<main data-dads-typeset>\n <dads-heading level=\"1\">一覧</dads-heading>\n <dads-table>\n <table>\n <thead>\n <tr><th>項目</th><th>値</th></tr>\n </thead>\n <tbody>\n <tr><td>サンプル</td><td>1</td></tr>\n </tbody>\n </table>\n </dads-table>\n <dads-page-navigation current=\"1\" total=\"3\"></dads-page-navigation>\n</main>\n",
|
|
61
|
+
"behavior": "document.querySelector(\"dads-page-navigation\").addEventListener(\"page-change\", (e) => {\n console.log(\"Page:\", e.detail.page);\n // Fetch and render table rows for the new page\n});"
|
|
34
62
|
},
|
|
35
63
|
"card-grid": {
|
|
36
64
|
"id": "card-grid",
|
|
37
65
|
"title": "カードグリッド",
|
|
38
66
|
"description": "カードで一覧表示する基本レイアウト",
|
|
39
|
-
"requires": [
|
|
67
|
+
"requires": [
|
|
68
|
+
"heading",
|
|
69
|
+
"card",
|
|
70
|
+
"button"
|
|
71
|
+
],
|
|
40
72
|
"stability": "stable",
|
|
41
73
|
"contractVersion": "1.0",
|
|
42
|
-
"entryHints": [
|
|
43
|
-
|
|
74
|
+
"entryHints": [
|
|
75
|
+
"boot",
|
|
76
|
+
"@wcf",
|
|
77
|
+
"index"
|
|
78
|
+
],
|
|
79
|
+
"html": "<main data-dads-typeset>\n <dads-heading level=\"1\">お知らせ</dads-heading>\n <section>\n <dads-card>\n <h2>カード1</h2>\n <dads-button variant=\"outlined\">詳細</dads-button>\n </dads-card>\n <dads-card>\n <h2>カード2</h2>\n <dads-button variant=\"outlined\">詳細</dads-button>\n </dads-card>\n </section>\n</main>\n",
|
|
80
|
+
"behavior": "document.querySelectorAll(\"dads-button\").forEach((btn) => {\n btn.addEventListener(\"click\", () => {\n console.log(\"Card detail clicked\");\n });\n});"
|
|
44
81
|
},
|
|
45
82
|
"layout-website-hero-section-footer": {
|
|
46
83
|
"id": "layout-website-hero-section-footer",
|
|
47
84
|
"title": "レイアウト(Website: Hero + Section + Footer)",
|
|
48
85
|
"description": "コンテンツ主導の1カラムサイト向けレイアウト。",
|
|
49
|
-
"requires": [
|
|
86
|
+
"requires": [
|
|
87
|
+
"layout-shell",
|
|
88
|
+
"heading",
|
|
89
|
+
"card",
|
|
90
|
+
"button"
|
|
91
|
+
],
|
|
50
92
|
"stability": "stable",
|
|
51
93
|
"contractVersion": "1.0",
|
|
52
|
-
"entryHints": [
|
|
53
|
-
|
|
94
|
+
"entryHints": [
|
|
95
|
+
"boot",
|
|
96
|
+
"@wcf",
|
|
97
|
+
"index"
|
|
98
|
+
],
|
|
99
|
+
"html": "<dads-layout-shell data-dads-typeset pattern=\"website\" mode=\"auto\">\n <header slot=\"header\">\n <dads-heading level=\"1\">くらしの手続きポータル</dads-heading>\n <p>必要な手続きを1つの画面で確認できます。</p>\n </header>\n <section>\n <dads-card>\n <dads-heading level=\"2\">はじめての方へ</dads-heading>\n <p>制度の概要と申請までの流れを案内します。</p>\n <dads-button variant=\"outlined\">詳しく見る</dads-button>\n </dads-card>\n </section>\n <footer slot=\"footer\">© Digital Service</footer>\n</dads-layout-shell>\n",
|
|
100
|
+
"behavior": "// Responsive: layout-shell handles mode automatically via mode=\"auto\""
|
|
54
101
|
},
|
|
55
102
|
"layout-app-shell": {
|
|
56
103
|
"id": "layout-app-shell",
|
|
57
104
|
"title": "レイアウト(App/SaaS: Header + Sidebar + Main)",
|
|
58
105
|
"description": "業務アプリ向けの標準App Shellレイアウト。",
|
|
59
|
-
"requires": [
|
|
106
|
+
"requires": [
|
|
107
|
+
"layout-shell",
|
|
108
|
+
"layout-sidebar",
|
|
109
|
+
"heading",
|
|
110
|
+
"card"
|
|
111
|
+
],
|
|
60
112
|
"stability": "stable",
|
|
61
113
|
"contractVersion": "1.0",
|
|
62
|
-
"entryHints": [
|
|
63
|
-
|
|
114
|
+
"entryHints": [
|
|
115
|
+
"boot",
|
|
116
|
+
"@wcf",
|
|
117
|
+
"index"
|
|
118
|
+
],
|
|
119
|
+
"html": "<dads-layout-shell data-dads-typeset pattern=\"app-shell\" mode=\"auto\">\n <div slot=\"header\">\n <dads-heading level=\"2\">業務ダッシュボード</dads-heading>\n </div>\n <dads-layout-sidebar slot=\"sidebar\">\n <ul>\n <li>案件一覧</li>\n <li>承認待ち</li>\n <li>設定</li>\n </ul>\n </dads-layout-sidebar>\n <section>\n <dads-card>\n <dads-heading level=\"3\">進捗サマリー</dads-heading>\n <p>主要KPIを表示します。</p>\n </dads-card>\n </section>\n</dads-layout-shell>\n",
|
|
120
|
+
"behavior": "// layout-shell + layout-sidebar handle responsive behavior via mode=\"auto\""
|
|
64
121
|
},
|
|
65
122
|
"layout-master-detail": {
|
|
66
123
|
"id": "layout-master-detail",
|
|
67
124
|
"title": "レイアウト(Master-Detail: Main + Aside)",
|
|
68
125
|
"description": "一覧 + 詳細を同時表示する2カラムレイアウト。",
|
|
69
|
-
"requires": [
|
|
126
|
+
"requires": [
|
|
127
|
+
"layout-shell",
|
|
128
|
+
"layout-aside",
|
|
129
|
+
"heading",
|
|
130
|
+
"table"
|
|
131
|
+
],
|
|
70
132
|
"stability": "stable",
|
|
71
133
|
"contractVersion": "1.0",
|
|
72
|
-
"entryHints": [
|
|
73
|
-
|
|
134
|
+
"entryHints": [
|
|
135
|
+
"boot",
|
|
136
|
+
"@wcf",
|
|
137
|
+
"index"
|
|
138
|
+
],
|
|
139
|
+
"html": "<dads-layout-shell data-dads-typeset pattern=\"master-detail\" mode=\"auto\">\n <section>\n <dads-heading level=\"2\">申請一覧</dads-heading>\n <dads-table>\n <table>\n <thead>\n <tr><th scope=\"col\">申請ID</th><th scope=\"col\">状態</th></tr>\n </thead>\n <tbody>\n <tr><td>A-1001</td><td>審査中</td></tr>\n <tr><td>A-1002</td><td>差戻し</td></tr>\n </tbody>\n </table>\n </dads-table>\n </section>\n <dads-layout-aside slot=\"aside\">\n <dads-heading level=\"3\">詳細情報</dads-heading>\n <p>選択中レコードの詳細を表示します。</p>\n </dads-layout-aside>\n</dads-layout-shell>\n",
|
|
140
|
+
"behavior": "// Master row click updates aside detail\ndocument.querySelector(\"dads-table\").addEventListener(\"row-select\", (e) => {\n const aside = document.querySelector(\"dads-layout-aside\");\n aside.textContent = \"Selected: \" + e.detail.id;\n});"
|
|
74
141
|
},
|
|
75
142
|
"application-form-single-validation": {
|
|
76
143
|
"id": "application-form-single-validation",
|
|
77
144
|
"title": "申請フォーム(1ページ・検証エラー)",
|
|
78
145
|
"description": "必須項目を含む1ページ申請フォームとバリデーションエラー表示",
|
|
79
|
-
"requires": [
|
|
146
|
+
"requires": [
|
|
147
|
+
"heading",
|
|
148
|
+
"fieldset",
|
|
149
|
+
"input-text",
|
|
150
|
+
"select",
|
|
151
|
+
"textarea",
|
|
152
|
+
"button"
|
|
153
|
+
],
|
|
80
154
|
"stability": "experimental",
|
|
81
155
|
"contractVersion": "1.0",
|
|
82
|
-
"entryHints": [
|
|
83
|
-
|
|
156
|
+
"entryHints": [
|
|
157
|
+
"boot",
|
|
158
|
+
"@wcf",
|
|
159
|
+
"index"
|
|
160
|
+
],
|
|
161
|
+
"html": "<main data-dads-typeset>\n <dads-heading level=\"1\">申請フォーム</dads-heading>\n <form id=\"application-form-single\">\n <dads-fieldset>\n <legend>申請情報</legend>\n <dads-input-text label=\"氏名\" name=\"name\" required error error-text=\"氏名は必須です\"></dads-input-text>\n <dads-select label=\"種別\" name=\"type\" required></dads-select>\n <dads-textarea label=\"理由\" name=\"reason\" required></dads-textarea>\n </dads-fieldset>\n <dads-button type=\"submit\">送信</dads-button>\n </form>\n</main>\n",
|
|
162
|
+
"behavior": "document.querySelector(\"#application-form-single\").addEventListener(\"submit\", (e) => {\n e.preventDefault();\n const form = e.target;\n let valid = true;\n form.querySelectorAll(\"[required]\").forEach((el) => {\n if (!el.value) { el.setAttribute(\"error\", \"\"); valid = false; }\n else { el.removeAttribute(\"error\"); }\n });\n if (valid) console.log(\"Submit OK\");\n});"
|
|
84
163
|
},
|
|
85
164
|
"application-form-step-validation": {
|
|
86
165
|
"id": "application-form-step-validation",
|
|
87
166
|
"title": "申請フォーム(ステップ・検証エラー)",
|
|
88
167
|
"description": "ステップナビゲーション付き申請フォームと検証エラー表示",
|
|
89
|
-
"requires": [
|
|
168
|
+
"requires": [
|
|
169
|
+
"heading",
|
|
170
|
+
"step-navigation",
|
|
171
|
+
"fieldset",
|
|
172
|
+
"input-text",
|
|
173
|
+
"button"
|
|
174
|
+
],
|
|
90
175
|
"stability": "experimental",
|
|
91
176
|
"contractVersion": "1.0",
|
|
92
|
-
"entryHints": [
|
|
93
|
-
|
|
177
|
+
"entryHints": [
|
|
178
|
+
"boot",
|
|
179
|
+
"@wcf",
|
|
180
|
+
"index"
|
|
181
|
+
],
|
|
182
|
+
"html": "<main data-dads-typeset>\n <dads-heading level=\"1\">申請フォーム(ステップ)</dads-heading>\n <dads-step-navigation></dads-step-navigation>\n <form id=\"application-form-step\">\n <dads-fieldset>\n <legend>ステップ1: 申請者情報</legend>\n <dads-input-text label=\"氏名\" name=\"name\" required error error-text=\"氏名は必須です\"></dads-input-text>\n </dads-fieldset>\n <dads-button type=\"submit\">次へ</dads-button>\n </form>\n</main>\n",
|
|
183
|
+
"behavior": "let step = 0;\ndocument.querySelector(\"#application-form-step\").addEventListener(\"submit\", (e) => {\n e.preventDefault();\n // Validate current step, advance step++\n console.log(\"Step\", step, \"validated\");\n});"
|
|
94
184
|
},
|
|
95
185
|
"mockup-website": {
|
|
96
186
|
"id": "mockup-website",
|
|
97
187
|
"title": "モックアップ(Website)",
|
|
98
188
|
"description": "Webサイト向けのヒーロー + セクション構成を device-mock で確認するモックアップ。",
|
|
99
|
-
"requires": [
|
|
189
|
+
"requires": [
|
|
190
|
+
"device-mock",
|
|
191
|
+
"layout-shell",
|
|
192
|
+
"heading",
|
|
193
|
+
"card",
|
|
194
|
+
"button"
|
|
195
|
+
],
|
|
100
196
|
"stability": "stable",
|
|
101
197
|
"contractVersion": "1.0",
|
|
102
|
-
"entryHints": [
|
|
103
|
-
|
|
198
|
+
"entryHints": [
|
|
199
|
+
"boot"
|
|
200
|
+
],
|
|
201
|
+
"html": "<section data-dads-typeset>\n <dads-device-mock device=\"desktop\">\n <dads-layout-shell pattern=\"website\" mode=\"desktop\">\n <header slot=\"header\">\n <dads-heading level=\"1\">公共サービス ポータル</dads-heading>\n <p>申請・確認・問い合わせを1つの画面で行えます。</p>\n </header>\n <section>\n <dads-card>\n <dads-heading level=\"2\">新着のお知らせ</dads-heading>\n <p>重要なお知らせを確認してください。</p>\n <dads-button variant=\"outlined\">詳細</dads-button>\n </dads-card>\n </section>\n <footer slot=\"footer\">© Digital Service</footer>\n </dads-layout-shell>\n </dads-device-mock>\n</section>\n",
|
|
202
|
+
"behavior": "// Mockups are visual-only; no runtime behavior needed."
|
|
104
203
|
},
|
|
105
204
|
"mockup-app-shell": {
|
|
106
205
|
"id": "mockup-app-shell",
|
|
107
206
|
"title": "モックアップ(App Shell)",
|
|
108
207
|
"description": "業務画面向けのヘッダー + サイドバー + メイン領域を device-mock で再現するモックアップ。",
|
|
109
|
-
"requires": [
|
|
208
|
+
"requires": [
|
|
209
|
+
"device-mock",
|
|
210
|
+
"layout-shell",
|
|
211
|
+
"layout-sidebar",
|
|
212
|
+
"heading",
|
|
213
|
+
"card"
|
|
214
|
+
],
|
|
110
215
|
"stability": "stable",
|
|
111
216
|
"contractVersion": "1.0",
|
|
112
|
-
"entryHints": [
|
|
113
|
-
|
|
217
|
+
"entryHints": [
|
|
218
|
+
"boot"
|
|
219
|
+
],
|
|
220
|
+
"html": "<section data-dads-typeset>\n <dads-device-mock device=\"desktop\">\n <dads-layout-shell pattern=\"app-shell\" mode=\"desktop\">\n <div slot=\"header\">\n <dads-heading level=\"2\">申請管理ダッシュボード</dads-heading>\n </div>\n <dads-layout-sidebar slot=\"sidebar\">\n <ul>\n <li>一覧</li>\n <li>承認待ち</li>\n <li>設定</li>\n </ul>\n </dads-layout-sidebar>\n <section>\n <dads-card>\n <dads-heading level=\"3\">本日の処理件数</dads-heading>\n <p>処理済み 128 件 / 未処理 24 件</p>\n </dads-card>\n </section>\n </dads-layout-shell>\n </dads-device-mock>\n</section>\n",
|
|
221
|
+
"behavior": "// Mockups are visual-only; no runtime behavior needed."
|
|
114
222
|
},
|
|
115
223
|
"mockup-mobile-form": {
|
|
116
224
|
"id": "mockup-mobile-form",
|
|
117
225
|
"title": "モックアップ(Mobile Form)",
|
|
118
226
|
"description": "モバイル端末上で入力フォームの配置と余白を確認するモックアップ。",
|
|
119
|
-
"requires": [
|
|
227
|
+
"requires": [
|
|
228
|
+
"device-mock",
|
|
229
|
+
"heading",
|
|
230
|
+
"fieldset",
|
|
231
|
+
"input-text",
|
|
232
|
+
"select",
|
|
233
|
+
"textarea",
|
|
234
|
+
"button"
|
|
235
|
+
],
|
|
120
236
|
"stability": "stable",
|
|
121
237
|
"contractVersion": "1.0",
|
|
122
|
-
"entryHints": [
|
|
123
|
-
|
|
238
|
+
"entryHints": [
|
|
239
|
+
"boot"
|
|
240
|
+
],
|
|
241
|
+
"html": "<section data-dads-typeset>\n <dads-device-mock device=\"mobile\" visible-height=\"560px\">\n <main>\n <dads-heading level=\"2\">申請フォーム</dads-heading>\n <form>\n <dads-fieldset>\n <legend>基本情報</legend>\n <dads-input-text label=\"氏名\" name=\"name\" required></dads-input-text>\n <dads-select label=\"種別\" name=\"type\" required></dads-select>\n <dads-textarea label=\"詳細\" name=\"detail\" required></dads-textarea>\n </dads-fieldset>\n <dads-button type=\"submit\">確認へ進む</dads-button>\n </form>\n </main>\n </dads-device-mock>\n</section>\n",
|
|
242
|
+
"behavior": "// Mockups are visual-only; no runtime behavior needed."
|
|
124
243
|
}
|
|
125
244
|
}
|
|
126
245
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@monoharada/wcf-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "MCP server for the web-components-factory design system. Provides component discovery, validation, and pattern-based UI composition without cloning the repository.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/server.mjs
CHANGED
|
@@ -21,6 +21,7 @@ const REPO_FILE_MAP = {
|
|
|
21
21
|
'custom-elements.json': 'custom-elements.json',
|
|
22
22
|
'install-registry.json': 'registry/install-registry.json',
|
|
23
23
|
'pattern-registry.json': 'registry/pattern-registry.json',
|
|
24
|
+
'component-selector-guide.json': 'registry/component-selector-guide.json',
|
|
24
25
|
'design-tokens.json': 'design-tokens.json',
|
|
25
26
|
'guidelines-index.json': 'guidelines-index.json',
|
|
26
27
|
'llms-full.txt': 'llms-full.txt',
|
package/validator.mjs
CHANGED
|
@@ -471,12 +471,14 @@ export function detectTokenMisuseInInlineStyles({
|
|
|
471
471
|
* filePath?: string;
|
|
472
472
|
* text: string;
|
|
473
473
|
* severity?: string;
|
|
474
|
+
* cemTagNames?: Set<string>;
|
|
474
475
|
* }} params
|
|
475
476
|
*/
|
|
476
477
|
export function detectAccessibilityMisuseInMarkup({
|
|
477
478
|
filePath = '<input>',
|
|
478
479
|
text,
|
|
479
480
|
severity = 'warning',
|
|
481
|
+
cemTagNames,
|
|
480
482
|
}) {
|
|
481
483
|
const diagnostics = [];
|
|
482
484
|
const lineStarts = computeLineIndex(text);
|
|
@@ -508,7 +510,9 @@ export function detectAccessibilityMisuseInMarkup({
|
|
|
508
510
|
});
|
|
509
511
|
}
|
|
510
512
|
|
|
511
|
-
const
|
|
513
|
+
const parsedAttrs = parseAttributes(attrChunk);
|
|
514
|
+
|
|
515
|
+
const roleAttr = parsedAttrs.find(({ name }) => String(name ?? '').toLowerCase() === 'role');
|
|
512
516
|
const roleValue = String(roleAttr?.value ?? '').trim().toLowerCase();
|
|
513
517
|
if (roleAttr && roleValue === 'alert') {
|
|
514
518
|
const attrName = 'role';
|
|
@@ -527,6 +531,35 @@ export function detectAccessibilityMisuseInMarkup({
|
|
|
527
531
|
hint: 'Replace role=\"alert\" with non-live text associated to the control.',
|
|
528
532
|
});
|
|
529
533
|
}
|
|
534
|
+
|
|
535
|
+
// Empty label / aria-label detection (v0.4.0, DD-26)
|
|
536
|
+
// Only check CEM-registered custom elements to avoid false positives on third-party elements
|
|
537
|
+
const isCemElement = cemTagNames ? cemTagNames.has(tag) : tag.includes('-');
|
|
538
|
+
if (isCemElement) {
|
|
539
|
+
const EMPTY_LABEL_CHECKS = [
|
|
540
|
+
{ attr: 'label', code: 'emptyLabel', hint: 'Set label to a descriptive text, e.g. label="氏名".', msg: (t) => `Empty label attribute on <${t}>. Provide a meaningful label for accessibility.` },
|
|
541
|
+
{ attr: 'aria-label', code: 'emptyAriaLabel', hint: 'Set aria-label to descriptive text or use a visible <label> element instead.', msg: (t) => `Empty aria-label attribute on <${t}>. Provide a meaningful label for accessibility.` },
|
|
542
|
+
];
|
|
543
|
+
for (const { name, offset, value } of parsedAttrs) {
|
|
544
|
+
const attrLower = String(name ?? '').toLowerCase();
|
|
545
|
+
if (typeof value !== 'string' || value.trim() !== '') continue;
|
|
546
|
+
const check = EMPTY_LABEL_CHECKS.find((c) => c.attr === attrLower);
|
|
547
|
+
if (!check) continue;
|
|
548
|
+
const startIndex = rawAttrsStart + offset;
|
|
549
|
+
const endIndex = startIndex + name.length;
|
|
550
|
+
const range = makeRange(lineStarts, startIndex, endIndex);
|
|
551
|
+
diagnostics.push({
|
|
552
|
+
file: filePath,
|
|
553
|
+
range,
|
|
554
|
+
severity,
|
|
555
|
+
code: check.code,
|
|
556
|
+
message: check.msg(tag),
|
|
557
|
+
tagName: tag,
|
|
558
|
+
attrName: check.attr,
|
|
559
|
+
hint: check.hint,
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
}
|
|
530
563
|
}
|
|
531
564
|
|
|
532
565
|
return diagnostics;
|
|
@@ -643,9 +676,18 @@ const PARENT_CHILD_CONSTRAINTS = new Map([
|
|
|
643
676
|
['dads-menu-list-item', 'dads-menu-list'],
|
|
644
677
|
]);
|
|
645
678
|
|
|
679
|
+
/**
|
|
680
|
+
* HTML void elements that never have a closing tag.
|
|
681
|
+
*/
|
|
682
|
+
const HTML_VOID_ELEMENTS = new Set([
|
|
683
|
+
'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',
|
|
684
|
+
'link', 'meta', 'source', 'track', 'wbr',
|
|
685
|
+
]);
|
|
686
|
+
|
|
646
687
|
/**
|
|
647
688
|
* Detect orphaned child components (child appears without expected parent).
|
|
648
|
-
* Uses
|
|
689
|
+
* Uses a lightweight tag-stack approach: only prefix-matching tags (e.g. dads-*)
|
|
690
|
+
* are tracked on the stack; all other HTML elements are ignored.
|
|
649
691
|
* @param {{
|
|
650
692
|
* filePath?: string;
|
|
651
693
|
* text: string;
|
|
@@ -664,7 +706,7 @@ export function detectOrphanedChildComponents({
|
|
|
664
706
|
const p = prefix.toLowerCase();
|
|
665
707
|
const canonicalPrefix = 'dads';
|
|
666
708
|
|
|
667
|
-
// Build prefix-aware constraint map
|
|
709
|
+
// Build prefix-aware constraint map (child → parent)
|
|
668
710
|
const constraints = new Map();
|
|
669
711
|
for (const [child, parent] of PARENT_CHILD_CONSTRAINTS.entries()) {
|
|
670
712
|
const mappedChild = p !== canonicalPrefix ? child.replace(canonicalPrefix, p) : child;
|
|
@@ -672,36 +714,56 @@ export function detectOrphanedChildComponents({
|
|
|
672
714
|
constraints.set(mappedChild, mappedParent);
|
|
673
715
|
}
|
|
674
716
|
|
|
675
|
-
const
|
|
717
|
+
const prefixDash = `${p}-`;
|
|
718
|
+
// Stack of currently open prefix-matching tags
|
|
719
|
+
const stack = [];
|
|
676
720
|
|
|
677
|
-
|
|
721
|
+
// Regex matches opening tags, closing tags, and self-closing tags
|
|
722
|
+
const tagRe = /<\/?([a-z][a-z0-9-]*)\b[^<>]*?\/?>/gi;
|
|
678
723
|
let m;
|
|
679
724
|
|
|
680
725
|
while ((m = tagRe.exec(text))) {
|
|
726
|
+
const fullMatch = m[0];
|
|
681
727
|
const tag = String(m[1] ?? '').toLowerCase();
|
|
682
|
-
const
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
//
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
728
|
+
const isClosing = fullMatch.startsWith('</');
|
|
729
|
+
const isSelfClosing = fullMatch.endsWith('/>');
|
|
730
|
+
|
|
731
|
+
// Only track prefix-matching tags on the stack
|
|
732
|
+
if (!tag.startsWith(prefixDash)) continue;
|
|
733
|
+
|
|
734
|
+
if (isClosing) {
|
|
735
|
+
// Pop the matching opening tag from the stack (search from top)
|
|
736
|
+
for (let i = stack.length - 1; i >= 0; i--) {
|
|
737
|
+
if (stack[i] === tag) {
|
|
738
|
+
stack.splice(i, 1);
|
|
739
|
+
break;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
continue;
|
|
743
|
+
}
|
|
689
744
|
|
|
690
|
-
|
|
691
|
-
const
|
|
745
|
+
// Check if this is a child that needs a parent
|
|
746
|
+
const expectedParent = constraints.get(tag);
|
|
747
|
+
if (expectedParent) {
|
|
748
|
+
const hasParent = stack.includes(expectedParent);
|
|
749
|
+
if (!hasParent) {
|
|
750
|
+
const tagOffset = m.index + 1;
|
|
751
|
+
const range = makeRange(lineStarts, tagOffset, tagOffset + tag.length);
|
|
752
|
+
diagnostics.push({
|
|
753
|
+
file: filePath,
|
|
754
|
+
range,
|
|
755
|
+
severity,
|
|
756
|
+
code: 'orphanedChildComponent',
|
|
757
|
+
message: `<${tag}> should be a child of <${expectedParent}>.`,
|
|
758
|
+
tagName: tag,
|
|
759
|
+
hint: `Wrap <${tag}> inside <${expectedParent}>...</${expectedParent}>.`,
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
}
|
|
692
763
|
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
diagnostics.push({
|
|
697
|
-
file: filePath,
|
|
698
|
-
range,
|
|
699
|
-
severity,
|
|
700
|
-
code: 'orphanedChildComponent',
|
|
701
|
-
message: `<${tag}> should be a child of <${expectedParent}>.`,
|
|
702
|
-
tagName: tag,
|
|
703
|
-
hint: `Wrap <${tag}> inside <${expectedParent}>...</${expectedParent}>.`,
|
|
704
|
-
});
|
|
764
|
+
// Push opening tag to stack (skip void elements and self-closing)
|
|
765
|
+
if (!isSelfClosing && !HTML_VOID_ELEMENTS.has(tag)) {
|
|
766
|
+
stack.push(tag);
|
|
705
767
|
}
|
|
706
768
|
}
|
|
707
769
|
|