@adia-ai/web-components 0.8.6 → 0.8.8
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/CHANGELOG.md +14 -0
- package/README.md +3 -3
- package/dist/host.min.css +1 -1
- package/dist/host.sheet.js +1 -1
- package/dist/theme-provider.min.js +2 -2
- package/dist/web-components.min.css +1 -1
- package/dist/web-components.min.js +1 -1
- package/dist/web-components.sheet.js +1 -1
- package/package.json +2 -1
- package/patterns/access-requests/access-requests.examples.html +132 -0
- package/patterns/access-requests/access-requests.html +60 -0
- package/patterns/admin-keys-and-export/admin-keys-and-export.examples.html +236 -0
- package/patterns/admin-keys-and-export/admin-keys-and-export.html +64 -0
- package/patterns/admin-shell/admin-shell.examples.html +327 -0
- package/patterns/admin-shell/admin-shell.html +44 -0
- package/patterns/agent-cost/agent-cost.examples.html +111 -0
- package/patterns/agent-cost/agent-cost.html +62 -0
- package/patterns/agent-memory/agent-memory.examples.html +155 -0
- package/patterns/agent-memory/agent-memory.html +64 -0
- package/patterns/agent-prompt-library/agent-prompt-library.examples.html +181 -0
- package/patterns/agent-prompt-library/agent-prompt-library.html +64 -0
- package/patterns/agent-tool-call/agent-tool-call.examples.html +123 -0
- package/patterns/agent-tool-call/agent-tool-call.html +60 -0
- package/patterns/alert-dialog/alert-dialog.examples.html +105 -0
- package/patterns/alert-dialog/alert-dialog.html +57 -0
- package/patterns/app-nav/app-nav.examples.html +421 -0
- package/patterns/app-nav/app-nav.html +50 -0
- package/patterns/approvals/approvals.examples.html +131 -0
- package/patterns/approvals/approvals.html +58 -0
- package/patterns/audit-log/audit-log.examples.html +74 -0
- package/patterns/audit-log/audit-log.examples.js +60 -0
- package/patterns/audit-log/audit-log.html +63 -0
- package/patterns/bulk-action-toolbar/bulk-action-toolbar.examples.html +89 -0
- package/patterns/bulk-action-toolbar/bulk-action-toolbar.examples.js +69 -0
- package/patterns/bulk-action-toolbar/bulk-action-toolbar.html +62 -0
- package/patterns/changelog-feed/changelog-feed.examples.html +151 -0
- package/patterns/changelog-feed/changelog-feed.html +57 -0
- package/patterns/chart-in-card/chart-in-card.examples.html +193 -0
- package/patterns/chart-in-card/chart-in-card.examples.js +46 -0
- package/patterns/chart-in-card/chart-in-card.html +66 -0
- package/patterns/chat-shell/chat-shell.examples.html +129 -0
- package/patterns/chat-shell/chat-shell.html +46 -0
- package/patterns/comments-and-collaboration/comments-and-collaboration.examples.html +241 -0
- package/patterns/comments-and-collaboration/comments-and-collaboration.html +66 -0
- package/patterns/component-tokens/component-tokens.examples.html +483 -0
- package/patterns/component-tokens/component-tokens.html +42 -0
- package/patterns/conversion-funnel/conversion-funnel.examples.html +158 -0
- package/patterns/conversion-funnel/conversion-funnel.examples.js +4 -0
- package/patterns/conversion-funnel/conversion-funnel.html +64 -0
- package/patterns/data-tables-inline-edit-and-tree/data-tables-inline-edit-and-tree.examples.html +196 -0
- package/patterns/data-tables-inline-edit-and-tree/data-tables-inline-edit-and-tree.html +58 -0
- package/patterns/diff-review/diff-review.examples.html +147 -0
- package/patterns/diff-review/diff-review.html +56 -0
- package/patterns/editor-shell/editor-shell.examples.html +139 -0
- package/patterns/editor-shell/editor-shell.html +62 -0
- package/patterns/export-flow/export-flow.examples.html +152 -0
- package/patterns/export-flow/export-flow.examples.js +37 -0
- package/patterns/export-flow/export-flow.html +65 -0
- package/patterns/filter-bar/filter-bar.examples.html +116 -0
- package/patterns/filter-bar/filter-bar.examples.js +23 -0
- package/patterns/filter-bar/filter-bar.html +70 -0
- package/patterns/form-drawer/form-drawer.examples.html +210 -0
- package/patterns/form-drawer/form-drawer.html +64 -0
- package/patterns/form-system/form-system.examples.html +383 -0
- package/patterns/form-system/form-system.html +48 -0
- package/patterns/forms-conditional-and-autocomplete/forms-conditional-and-autocomplete.examples.html +189 -0
- package/patterns/forms-conditional-and-autocomplete/forms-conditional-and-autocomplete.html +64 -0
- package/patterns/inline-dialog/inline-dialog.examples.html +143 -0
- package/patterns/inline-dialog/inline-dialog.html +59 -0
- package/patterns/kanban-board/kanban-board.examples.html +224 -0
- package/patterns/kanban-board/kanban-board.html +58 -0
- package/patterns/layout/layout.examples.html +365 -0
- package/patterns/layout/layout.html +54 -0
- package/patterns/marketing-engagement/marketing-engagement.examples.html +280 -0
- package/patterns/marketing-engagement/marketing-engagement.html +64 -0
- package/patterns/notifications-bell-and-digest/notifications-bell-and-digest.examples.html +213 -0
- package/patterns/notifications-bell-and-digest/notifications-bell-and-digest.html +58 -0
- package/patterns/overview/overview.examples.html +119 -0
- package/patterns/overview/overview.html +42 -0
- package/patterns/permissions-matrix/permissions-matrix.examples.html +91 -0
- package/patterns/permissions-matrix/permissions-matrix.examples.js +28 -0
- package/patterns/permissions-matrix/permissions-matrix.html +56 -0
- package/patterns/permissions-role-picker/permissions-role-picker.examples.html +81 -0
- package/patterns/permissions-role-picker/permissions-role-picker.html +56 -0
- package/patterns/permissions-sharing/permissions-sharing.examples.html +175 -0
- package/patterns/permissions-sharing/permissions-sharing.html +66 -0
- package/patterns/profile-public-and-verification/profile-public-and-verification.examples.html +227 -0
- package/patterns/profile-public-and-verification/profile-public-and-verification.html +70 -0
- package/patterns/record-detail-drawer/record-detail-drawer.examples.html +164 -0
- package/patterns/record-detail-drawer/record-detail-drawer.html +64 -0
- package/patterns/responsive-shell-sidebar/README.md +37 -0
- package/patterns/responsive-shell-sidebar/responsive-shell-sidebar.css +39 -0
- package/patterns/responsive-shell-sidebar/responsive-shell-sidebar.examples.html +208 -0
- package/patterns/responsive-shell-sidebar/responsive-shell-sidebar.html +47 -0
- package/patterns/retention-cohort/retention-cohort.examples.html +149 -0
- package/patterns/retention-cohort/retention-cohort.examples.js +37 -0
- package/patterns/retention-cohort/retention-cohort.html +62 -0
- package/patterns/search-discovery/search-discovery.examples.html +263 -0
- package/patterns/search-discovery/search-discovery.html +58 -0
- package/patterns/system-banners/system-banners.examples.html +150 -0
- package/patterns/system-banners/system-banners.html +52 -0
- package/patterns/timezone-selector/timezone-selector.examples.html +82 -0
- package/patterns/timezone-selector/timezone-selector.examples.js +72 -0
- package/patterns/timezone-selector/timezone-selector.html +57 -0
- package/patterns/wizard-drawer/wizard-drawer.examples.html +144 -0
- package/patterns/wizard-drawer/wizard-drawer.examples.js +33 -0
- package/patterns/wizard-drawer/wizard-drawer.html +72 -0
- package/styles/type/roles.css +8 -8
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
<header>
|
|
2
|
+
<div>
|
|
3
|
+
<h1>Form System</h1>
|
|
4
|
+
</div>
|
|
5
|
+
<p>AdiaUI's form system uses <code>UIFormElement</code> and <code>ElementInternals</code> to give custom elements native form participation, constraint validation, and auto-validation UX.</p>
|
|
6
|
+
</header>
|
|
7
|
+
|
|
8
|
+
<section data-section data-property="overview">
|
|
9
|
+
<h2 variant="section">Overview</h2>
|
|
10
|
+
<p>
|
|
11
|
+
HTML forms expect native elements (<code><input></code>, <code><select></code>)
|
|
12
|
+
to participate in submission, validation, and reset. Custom elements are invisible to
|
|
13
|
+
forms by default. AdiaUI solves this with <code>UIFormElement</code>, a base class
|
|
14
|
+
that bridges custom elements into the native form lifecycle via the
|
|
15
|
+
<code>ElementInternals</code> API.
|
|
16
|
+
</p>
|
|
17
|
+
<p>
|
|
18
|
+
Any component that extends <code>UIFormElement</code> can be placed inside a
|
|
19
|
+
<code><form></code> and will:
|
|
20
|
+
</p>
|
|
21
|
+
<ul>
|
|
22
|
+
<li>Submit its value with the form (via <code>name</code> attribute)</li>
|
|
23
|
+
<li>Participate in constraint validation (required, pattern, minlength, maxlength)</li>
|
|
24
|
+
<li>Surface validation errors automatically on submit, blur, and input</li>
|
|
25
|
+
<li>Reset when the form resets</li>
|
|
26
|
+
<li>Be disabled when the form or fieldset is disabled</li>
|
|
27
|
+
</ul>
|
|
28
|
+
</section>
|
|
29
|
+
|
|
30
|
+
<section data-section data-property="components-using-uiformelement">
|
|
31
|
+
<h2 variant="section">Components Using UIFormElement</h2>
|
|
32
|
+
<p>
|
|
33
|
+
The following components extend <code>UIFormElement</code> and participate in forms:
|
|
34
|
+
</p>
|
|
35
|
+
<table>
|
|
36
|
+
<thead>
|
|
37
|
+
<tr>
|
|
38
|
+
<th>Component</th>
|
|
39
|
+
<th>Tag</th>
|
|
40
|
+
<th>Value Type</th>
|
|
41
|
+
</tr>
|
|
42
|
+
</thead>
|
|
43
|
+
<tbody>
|
|
44
|
+
<tr><td>Input</td><td><code>input-ui</code></td><td>String</td></tr>
|
|
45
|
+
<tr><td>Textarea</td><td><code>textarea-ui</code></td><td>String</td></tr>
|
|
46
|
+
<tr><td>Select</td><td><code>select-ui</code></td><td>String</td></tr>
|
|
47
|
+
<tr><td>Check</td><td><code>check-ui</code></td><td>String (on/off)</td></tr>
|
|
48
|
+
<tr><td>Switch</td><td><code>switch-ui</code></td><td>String (on/off)</td></tr>
|
|
49
|
+
<tr><td>Radio</td><td><code>radio-ui</code></td><td>String</td></tr>
|
|
50
|
+
<tr><td>Slider</td><td><code>slider-ui</code></td><td>Number (as string)</td></tr>
|
|
51
|
+
<tr><td>Range</td><td><code>range-ui</code></td><td>Number (as string)</td></tr>
|
|
52
|
+
<tr><td>Search</td><td><code>search-ui</code></td><td>String</td></tr>
|
|
53
|
+
<tr><td>Segmented</td><td><code>segmented-ui</code></td><td>String</td></tr>
|
|
54
|
+
<tr><td>OTP Input</td><td><code>otp-input-ui</code></td><td>String</td></tr>
|
|
55
|
+
<tr><td>Calendar Picker</td><td><code>calendar-picker-ui</code></td><td>Date string</td></tr>
|
|
56
|
+
<tr><td>Color Picker</td><td><code>color-picker-ui</code></td><td>Color string</td></tr>
|
|
57
|
+
<tr><td>Upload</td><td><code>upload-ui</code></td><td>File(s)</td></tr>
|
|
58
|
+
</tbody>
|
|
59
|
+
</table>
|
|
60
|
+
</section>
|
|
61
|
+
|
|
62
|
+
<section data-section data-property="elementinternals">
|
|
63
|
+
<h2 variant="section">ElementInternals</h2>
|
|
64
|
+
<p>
|
|
65
|
+
<code>ElementInternals</code> is the browser API that lets custom elements act as form
|
|
66
|
+
controls. AdiaUI's <code>UIFormElement</code> sets <code>static formAssociated = true</code>
|
|
67
|
+
to opt in, then uses the internals object to:
|
|
68
|
+
</p>
|
|
69
|
+
<ul>
|
|
70
|
+
<li><code>setFormValue(val)</code> — set the value submitted with the form</li>
|
|
71
|
+
<li><code>setValidity(flags, message, anchor)</code> — set validation state</li>
|
|
72
|
+
<li><code>checkValidity()</code> / <code>reportValidity()</code> — trigger validation</li>
|
|
73
|
+
<li>Access <code>.form</code>, <code>.labels</code>, <code>.validity</code></li>
|
|
74
|
+
</ul>
|
|
75
|
+
<code-ui language="javascript">class UIFormElement extends UIElement {
|
|
76
|
+
static formAssociated = true;
|
|
77
|
+
|
|
78
|
+
// Provided by the browser when formAssociated is true:
|
|
79
|
+
// this.internals = this.attachInternals();
|
|
80
|
+
|
|
81
|
+
get form() { return this.internals.form; }
|
|
82
|
+
get validity() { return this.internals.validity; }
|
|
83
|
+
get validationMessage() { return this.internals.validationMessage; }
|
|
84
|
+
}</code-ui>
|
|
85
|
+
</section>
|
|
86
|
+
|
|
87
|
+
<section data-section data-property="constraint-validation">
|
|
88
|
+
<h2 variant="section">Constraint Validation</h2>
|
|
89
|
+
<p>
|
|
90
|
+
<code>UIFormElement</code> implements four constraint types that mirror native HTML
|
|
91
|
+
validation:
|
|
92
|
+
</p>
|
|
93
|
+
<table>
|
|
94
|
+
<thead>
|
|
95
|
+
<tr>
|
|
96
|
+
<th>Attribute</th>
|
|
97
|
+
<th>Validity Flag</th>
|
|
98
|
+
<th>Default Message</th>
|
|
99
|
+
</tr>
|
|
100
|
+
</thead>
|
|
101
|
+
<tbody>
|
|
102
|
+
<tr>
|
|
103
|
+
<td><code>required</code></td>
|
|
104
|
+
<td><code>valueMissing</code></td>
|
|
105
|
+
<td>"This field is required."</td>
|
|
106
|
+
</tr>
|
|
107
|
+
<tr>
|
|
108
|
+
<td><code>pattern="regex"</code></td>
|
|
109
|
+
<td><code>patternMismatch</code></td>
|
|
110
|
+
<td>"Please match the requested format."</td>
|
|
111
|
+
</tr>
|
|
112
|
+
<tr>
|
|
113
|
+
<td><code>minlength="n"</code></td>
|
|
114
|
+
<td><code>tooShort</code></td>
|
|
115
|
+
<td>"Please use at least n characters."</td>
|
|
116
|
+
</tr>
|
|
117
|
+
<tr>
|
|
118
|
+
<td><code>maxlength="n"</code></td>
|
|
119
|
+
<td><code>tooLong</code></td>
|
|
120
|
+
<td>"Please use no more than n characters."</td>
|
|
121
|
+
</tr>
|
|
122
|
+
</tbody>
|
|
123
|
+
</table>
|
|
124
|
+
<code-ui language="html"><form>
|
|
125
|
+
<input-ui name="email" required pattern="[^@]+@[^@]+"
|
|
126
|
+
data-msg-required="Email is required"
|
|
127
|
+
data-msg-pattern="Enter a valid email">
|
|
128
|
+
</input-ui>
|
|
129
|
+
|
|
130
|
+
<input-ui name="username" required minlength="3" maxlength="20"
|
|
131
|
+
data-msg-minlength="Username must be at least 3 characters"
|
|
132
|
+
data-msg-maxlength="Username cannot exceed 20 characters">
|
|
133
|
+
</input-ui>
|
|
134
|
+
|
|
135
|
+
<button-ui type="submit" text="Submit" variant="primary"></button-ui>
|
|
136
|
+
</form></code-ui>
|
|
137
|
+
</section>
|
|
138
|
+
|
|
139
|
+
<section data-section data-property="auto-validation-lifecycle">
|
|
140
|
+
<h2 variant="section">Auto-Validation Lifecycle</h2>
|
|
141
|
+
<p>
|
|
142
|
+
AdiaUI implements a progressive validation UX that avoids showing errors prematurely.
|
|
143
|
+
The lifecycle is:
|
|
144
|
+
</p>
|
|
145
|
+
<ol>
|
|
146
|
+
<li><strong>On form submit:</strong> The browser fires the <code>invalid</code> event on any
|
|
147
|
+
field that fails validation. <code>UIFormElement</code> catches this, sets
|
|
148
|
+
<code>aria-invalid="true"</code>, and populates the <code>error</code> attribute with
|
|
149
|
+
the validation message.</li>
|
|
150
|
+
<li><strong>On input (while invalid):</strong> If the field currently has an error, every
|
|
151
|
+
keystroke re-validates. As soon as the value passes, the error is cleared immediately.
|
|
152
|
+
This gives instant positive feedback.</li>
|
|
153
|
+
<li><strong>On blur (if dirty):</strong> When the user tabs away from a field they have
|
|
154
|
+
typed in, validation runs. This catches errors on fields the user has interacted with
|
|
155
|
+
but not submitted.</li>
|
|
156
|
+
<li><strong>On form reset:</strong> All validation state is cleared — <code>error</code>
|
|
157
|
+
attribute removed, <code>aria-invalid</code> removed, dirty flag reset.</li>
|
|
158
|
+
</ol>
|
|
159
|
+
<p data-note>
|
|
160
|
+
The "dirty" flag ensures blur validation only fires after the user has actually
|
|
161
|
+
interacted with the field. A pristine field that receives focus and then loses it
|
|
162
|
+
will not show an error.
|
|
163
|
+
</p>
|
|
164
|
+
</section>
|
|
165
|
+
|
|
166
|
+
<section data-section data-property="custom-error-messages">
|
|
167
|
+
<h2 variant="section">Custom Error Messages</h2>
|
|
168
|
+
<p>
|
|
169
|
+
Override the default validation messages using <code>data-msg-*</code> attributes
|
|
170
|
+
on any form element:
|
|
171
|
+
</p>
|
|
172
|
+
<table>
|
|
173
|
+
<thead>
|
|
174
|
+
<tr>
|
|
175
|
+
<th>Attribute</th>
|
|
176
|
+
<th>Overrides</th>
|
|
177
|
+
</tr>
|
|
178
|
+
</thead>
|
|
179
|
+
<tbody>
|
|
180
|
+
<tr>
|
|
181
|
+
<td><code>data-msg-required</code></td>
|
|
182
|
+
<td>Message when field is empty and <code>required</code></td>
|
|
183
|
+
</tr>
|
|
184
|
+
<tr>
|
|
185
|
+
<td><code>data-msg-pattern</code></td>
|
|
186
|
+
<td>Message when value doesn't match <code>pattern</code></td>
|
|
187
|
+
</tr>
|
|
188
|
+
<tr>
|
|
189
|
+
<td><code>data-msg-minlength</code></td>
|
|
190
|
+
<td>Message when value is shorter than <code>minlength</code></td>
|
|
191
|
+
</tr>
|
|
192
|
+
<tr>
|
|
193
|
+
<td><code>data-msg-maxlength</code></td>
|
|
194
|
+
<td>Message when value exceeds <code>maxlength</code></td>
|
|
195
|
+
</tr>
|
|
196
|
+
</tbody>
|
|
197
|
+
</table>
|
|
198
|
+
<code-ui language="html"><input-ui
|
|
199
|
+
name="phone"
|
|
200
|
+
required
|
|
201
|
+
pattern="\d{3}-\d{3}-\d{4}"
|
|
202
|
+
data-msg-required="Phone number is required"
|
|
203
|
+
data-msg-pattern="Use format: 123-456-7890">
|
|
204
|
+
</input-ui></code-ui>
|
|
205
|
+
</section>
|
|
206
|
+
|
|
207
|
+
<section data-section data-property="manual-control">
|
|
208
|
+
<h2 variant="section">Manual Control</h2>
|
|
209
|
+
<p>
|
|
210
|
+
For cases where you need to validate programmatically or set custom errors
|
|
211
|
+
(e.g. server-side validation), use these methods:
|
|
212
|
+
</p>
|
|
213
|
+
<table>
|
|
214
|
+
<thead>
|
|
215
|
+
<tr>
|
|
216
|
+
<th>Method</th>
|
|
217
|
+
<th>Returns</th>
|
|
218
|
+
<th>Description</th>
|
|
219
|
+
</tr>
|
|
220
|
+
</thead>
|
|
221
|
+
<tbody>
|
|
222
|
+
<tr>
|
|
223
|
+
<td><code>validate()</code></td>
|
|
224
|
+
<td><code>boolean</code></td>
|
|
225
|
+
<td>Runs all constraints, surfaces error if invalid, clears if valid</td>
|
|
226
|
+
</tr>
|
|
227
|
+
<tr>
|
|
228
|
+
<td><code>setInvalid(message)</code></td>
|
|
229
|
+
<td><code>void</code></td>
|
|
230
|
+
<td>Sets a custom error — useful for async/server validation</td>
|
|
231
|
+
</tr>
|
|
232
|
+
<tr>
|
|
233
|
+
<td><code>setValid()</code></td>
|
|
234
|
+
<td><code>void</code></td>
|
|
235
|
+
<td>Clears all validation errors and <code>aria-invalid</code></td>
|
|
236
|
+
</tr>
|
|
237
|
+
<tr>
|
|
238
|
+
<td><code>syncValue(val)</code></td>
|
|
239
|
+
<td><code>void</code></td>
|
|
240
|
+
<td>Updates the form value and re-runs constraint validation</td>
|
|
241
|
+
</tr>
|
|
242
|
+
</tbody>
|
|
243
|
+
</table>
|
|
244
|
+
<code-ui language="javascript">const emailField = document.querySelector('input-ui[name="email"]');
|
|
245
|
+
|
|
246
|
+
// Programmatic validation
|
|
247
|
+
if (!emailField.validate()) {
|
|
248
|
+
console.log(emailField.validationMessage);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Server-side error
|
|
252
|
+
async function onSubmit() {
|
|
253
|
+
const res = await fetch('/api/check-email', { ... });
|
|
254
|
+
if (!res.ok) {
|
|
255
|
+
emailField.setInvalid('This email is already registered');
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Clear error after user action
|
|
260
|
+
emailField.setValid();
|
|
261
|
+
|
|
262
|
+
// Update form value programmatically
|
|
263
|
+
emailField.syncValue('new@example.com');</code-ui>
|
|
264
|
+
</section>
|
|
265
|
+
|
|
266
|
+
<section data-section data-property="form-lifecycle-callbacks">
|
|
267
|
+
<h2 variant="section">Form Lifecycle Callbacks</h2>
|
|
268
|
+
<p>
|
|
269
|
+
<code>UIFormElement</code> implements the standard form-associated custom element
|
|
270
|
+
callbacks. Subclasses can override the hook methods:
|
|
271
|
+
</p>
|
|
272
|
+
<table>
|
|
273
|
+
<thead>
|
|
274
|
+
<tr>
|
|
275
|
+
<th>Browser Callback</th>
|
|
276
|
+
<th>Override Hook</th>
|
|
277
|
+
<th>When It Fires</th>
|
|
278
|
+
</tr>
|
|
279
|
+
</thead>
|
|
280
|
+
<tbody>
|
|
281
|
+
<tr>
|
|
282
|
+
<td><code>formResetCallback()</code></td>
|
|
283
|
+
<td><code>onFormReset()</code></td>
|
|
284
|
+
<td>When the parent form is reset</td>
|
|
285
|
+
</tr>
|
|
286
|
+
<tr>
|
|
287
|
+
<td><code>formDisabledCallback(disabled)</code></td>
|
|
288
|
+
<td><code>onFormDisabled(disabled)</code></td>
|
|
289
|
+
<td>When a parent fieldset is disabled/enabled</td>
|
|
290
|
+
</tr>
|
|
291
|
+
<tr>
|
|
292
|
+
<td><code>formAssociatedCallback(form)</code></td>
|
|
293
|
+
<td><code>onFormAssociated(form)</code></td>
|
|
294
|
+
<td>When the element is associated with a form</td>
|
|
295
|
+
</tr>
|
|
296
|
+
<tr>
|
|
297
|
+
<td><code>formStateRestoreCallback(state, reason)</code></td>
|
|
298
|
+
<td><code>onFormStateRestore(state, reason)</code></td>
|
|
299
|
+
<td>When browser restores form state (back/forward navigation)</td>
|
|
300
|
+
</tr>
|
|
301
|
+
</tbody>
|
|
302
|
+
</table>
|
|
303
|
+
</section>
|
|
304
|
+
|
|
305
|
+
<section data-section data-property="live-demo">
|
|
306
|
+
<h2 variant="section">Live Demo</h2>
|
|
307
|
+
<p>Try submitting the form below without filling in the fields to see auto-validation in action:</p>
|
|
308
|
+
<div data-artifact-container>
|
|
309
|
+
<div data-artifact-item data-artifact-label="form">
|
|
310
|
+
<form data-chunk="form-system-live-demo" style="display:flex; flex-direction:column; gap:var(--a-space-3); max-width:400px;" onsubmit="event.preventDefault();">
|
|
311
|
+
<field-ui label="Full Name">
|
|
312
|
+
<input-ui name="fullname" required minlength="2" data-msg-required="Please enter your name" data-msg-minlength="Name must be at least 2 characters">
|
|
313
|
+
</input-ui>
|
|
314
|
+
</field-ui>
|
|
315
|
+
|
|
316
|
+
<field-ui label="Email">
|
|
317
|
+
<input-ui name="email" required pattern="[^@\s]+@[^@\s]+\.[^@\s]+" data-msg-required="Email is required" data-msg-pattern="Please enter a valid email address">
|
|
318
|
+
</input-ui>
|
|
319
|
+
</field-ui>
|
|
320
|
+
|
|
321
|
+
<field-ui label="Username">
|
|
322
|
+
<input-ui name="username" required minlength="3" maxlength="15" pattern="[a-z0-9_]+" data-msg-required="Username is required" data-msg-minlength="At least 3 characters" data-msg-maxlength="No more than 15 characters" data-msg-pattern="Lowercase letters, numbers, and underscores only">
|
|
323
|
+
</input-ui>
|
|
324
|
+
</field-ui>
|
|
325
|
+
|
|
326
|
+
<div style="display:flex; gap:var(--a-space-2);">
|
|
327
|
+
<button-ui type="submit" text="Submit" variant="primary"></button-ui>
|
|
328
|
+
<button-ui type="reset" text="Reset" variant="outline"></button-ui>
|
|
329
|
+
</div>
|
|
330
|
+
</form>
|
|
331
|
+
</div>
|
|
332
|
+
</div>
|
|
333
|
+
</section>
|
|
334
|
+
|
|
335
|
+
<section data-section data-property="extending-uiformelement">
|
|
336
|
+
<h2 variant="section">Extending UIFormElement</h2>
|
|
337
|
+
<p>
|
|
338
|
+
To create a new form-participating component, extend <code>UIFormElement</code>
|
|
339
|
+
and call <code>syncValue()</code> whenever the internal value changes:
|
|
340
|
+
</p>
|
|
341
|
+
<code-ui language="javascript">import { UIFormElement } from '@core/form.js';
|
|
342
|
+
|
|
343
|
+
class MyRating extends UIFormElement {
|
|
344
|
+
static properties = {
|
|
345
|
+
...UIFormElement.properties,
|
|
346
|
+
max: { type: Number, default: 5, reflect: true },
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
render() {
|
|
350
|
+
// Render stars, handle clicks...
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
#onStarClick(rating) {
|
|
354
|
+
this.value = String(rating);
|
|
355
|
+
this.syncValue(this.value); // updates form + runs validation
|
|
356
|
+
this.dispatch('change', { value: this.value });
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
onFormReset() {
|
|
360
|
+
this.value = '';
|
|
361
|
+
// Reset visual state...
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
customElements.define('my-rating', MyRating);</code-ui>
|
|
366
|
+
<p data-note>
|
|
367
|
+
Always call <code>syncValue()</code> after changing the component's value. This
|
|
368
|
+
updates the form's submission data and runs constraint validation so errors
|
|
369
|
+
clear as soon as the value becomes valid.
|
|
370
|
+
</p>
|
|
371
|
+
</section>
|
|
372
|
+
|
|
373
|
+
<section data-section data-property="rules">
|
|
374
|
+
<h2 variant="section">Authoring rules</h2>
|
|
375
|
+
<ul>
|
|
376
|
+
<li><strong>Extend <code>UIFormElement</code>, not <code>UIElement</code></strong>, when the component must participate in native form submission. <code>UIFormElement</code> handles <code>ElementInternals</code>, constraint validation, and the dirty-state lifecycle automatically.</li>
|
|
377
|
+
<li><strong>Always call <code>syncValue()</code></strong> after changing the component's internal value. This updates the form's submission data and runs constraint validation so errors clear as soon as the value becomes valid.</li>
|
|
378
|
+
<li><strong>Do not manipulate the <code>field-ui</code> error attribute directly.</strong> Use <code>setCustomError()</code> and <code>clearErrors()</code> from the base class — these coordinate with the field wrapper's ARIA live region.</li>
|
|
379
|
+
<li><strong>Every form control needs a <code>name</code> attribute</strong> for its value to be included in <code>FormData</code> on submit. Components without <code>name</code> are invisible to the form.</li>
|
|
380
|
+
<li><strong>Required validation belongs on <code>field-ui required</code></strong>, not only on the inner control. The field wrapper surfaces the asterisk and the ARIA required state; the inner control handles native constraint validation.</li>
|
|
381
|
+
<li><strong>Never bypass the auto-validation lifecycle</strong> by setting <code>validity.valid = false</code> from the outside. If you need a custom error from application code, use the MCP <code>submit_feedback</code> pattern or call <code>setCustomValidity()</code> on the internal form element.</li>
|
|
382
|
+
</ul>
|
|
383
|
+
</section>
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en" theme="auto">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>Form System (pattern) — AdiaUI</title>
|
|
7
|
+
|
|
8
|
+
<!-- Token base -->
|
|
9
|
+
<link rel="stylesheet" href="../../styles/resets.css">
|
|
10
|
+
<link rel="stylesheet" href="../../styles/tokens.css">
|
|
11
|
+
|
|
12
|
+
<!-- Component CSS — primitives this pattern composes -->
|
|
13
|
+
<link rel="stylesheet" href="../../components/button/button.css">
|
|
14
|
+
<link rel="stylesheet" href="../../components/code/code.css">
|
|
15
|
+
<link rel="stylesheet" href="../../components/field/field.css">
|
|
16
|
+
<link rel="stylesheet" href="../../components/input/input.css">
|
|
17
|
+
|
|
18
|
+
<!-- Component JS -->
|
|
19
|
+
<script type="module" src="../../components/button/button.js"></script>
|
|
20
|
+
<script type="module" src="../../components/code/code.js"></script>
|
|
21
|
+
<script type="module" src="../../components/field/field.js"></script>
|
|
22
|
+
<script type="module" src="../../components/input/input.js"></script>
|
|
23
|
+
|
|
24
|
+
<style>
|
|
25
|
+
:where(html, body) { margin: 0; min-height: 100vh; background: var(--md-sys-color-neutral-background); color: var(--md-sys-color-neutral-on-surface); font-family: var(--a-font-family); }
|
|
26
|
+
main { max-width: 960px; margin-inline: auto; padding: var(--a-space-6) var(--a-space-5); }
|
|
27
|
+
</style>
|
|
28
|
+
</head>
|
|
29
|
+
<body>
|
|
30
|
+
|
|
31
|
+
<main id="demo-root">
|
|
32
|
+
<p>Loading examples…</p>
|
|
33
|
+
</main>
|
|
34
|
+
|
|
35
|
+
<script type="module">
|
|
36
|
+
const root = document.getElementById('demo-root');
|
|
37
|
+
try {
|
|
38
|
+
const res = await fetch('./form-system.examples.html');
|
|
39
|
+
if (!res.ok) throw new Error(`fetch failed (${res.status})`);
|
|
40
|
+
root.innerHTML = await res.text();
|
|
41
|
+
} catch (err) {
|
|
42
|
+
root.innerHTML = `<p style="color:var(--a-danger-strong);">Failed to load form-system.examples.html — ${err.message}</p>`;
|
|
43
|
+
console.error('[form-system.html]', err);
|
|
44
|
+
}
|
|
45
|
+
</script>
|
|
46
|
+
|
|
47
|
+
</body>
|
|
48
|
+
</html>
|
package/patterns/forms-conditional-and-autocomplete/forms-conditional-and-autocomplete.examples.html
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
<header>
|
|
2
|
+
<div>
|
|
3
|
+
<h1>Conditional fields & address autocomplete</h1>
|
|
4
|
+
<div data-actions>
|
|
5
|
+
<tag-ui size="sm">field-ui</tag-ui> <tag-ui size="sm">input-ui</tag-ui> <tag-ui size="sm">select-ui</tag-ui> <tag-ui size="sm">card-ui</tag-ui> <tag-ui size="sm">radio-ui</tag-ui>
|
|
6
|
+
</div>
|
|
7
|
+
</div>
|
|
8
|
+
<p>Two adjacent form patterns: revealing fields based on prior answers (07i) and address-input that resolves a free-text query into structured fields (07j). Both compose existing form primitives — the pattern is in the wiring, not in new components.</p>
|
|
9
|
+
</header>
|
|
10
|
+
|
|
11
|
+
<section data-section data-property="when-to-use">
|
|
12
|
+
<h2 variant="section">When to use</h2>
|
|
13
|
+
<p data-note>
|
|
14
|
+
Conditional reveal: when the field set depends on a categorical
|
|
15
|
+
answer ("If 'Other', specify…", "If 'Business', show tax ID").
|
|
16
|
+
Address autocomplete: any place a user enters a postal address
|
|
17
|
+
that downstream systems need structured (billing, shipping,
|
|
18
|
+
KYC, support tickets).
|
|
19
|
+
</p>
|
|
20
|
+
</section>
|
|
21
|
+
|
|
22
|
+
<section data-section data-property="conditional-reveal">
|
|
23
|
+
<h2 variant="section">Conditional reveal</h2>
|
|
24
|
+
<p data-note>A select or radio determines which sub-fields render. Hidden fields are not just `display: none` — they're removed from the form so they don't submit. When revealed, they animate in and the form's height grows; focus moves to the first revealed field.</p>
|
|
25
|
+
<div data-artifact-container>
|
|
26
|
+
<div data-artifact-item data-artifact-label="conditional fields">
|
|
27
|
+
<card-ui data-chunk="forms-conditional-reveal" style="max-width: 560px;">
|
|
28
|
+
<header>
|
|
29
|
+
<span slot="heading" variant="section">Account type</span>
|
|
30
|
+
<text-ui slot="description" color="subtle">We'll show the fields you need based on this</text-ui>
|
|
31
|
+
</header>
|
|
32
|
+
<section>
|
|
33
|
+
<col-ui gap="3">
|
|
34
|
+
<field-ui label="Account type" required>
|
|
35
|
+
<select-ui name="account_type">
|
|
36
|
+
<option value="personal">Personal</option>
|
|
37
|
+
<option value="business" selected>Business</option>
|
|
38
|
+
<option value="nonprofit">Non-profit</option>
|
|
39
|
+
</select-ui>
|
|
40
|
+
</field-ui>
|
|
41
|
+
<field-ui label="Legal entity name" hint="Matches your business registration" required>
|
|
42
|
+
<input-ui name="entity_name" placeholder="Adia Labs, Inc."></input-ui>
|
|
43
|
+
</field-ui>
|
|
44
|
+
<field-ui label="Tax ID / EIN" required>
|
|
45
|
+
<input-ui name="tax_id" placeholder="00-0000000"></input-ui>
|
|
46
|
+
</field-ui>
|
|
47
|
+
<field-ui label="Industry">
|
|
48
|
+
<select-ui name="industry">
|
|
49
|
+
<option value="">Choose one…</option>
|
|
50
|
+
<option value="software">Software</option>
|
|
51
|
+
<option value="retail">Retail</option>
|
|
52
|
+
<option value="services">Services</option>
|
|
53
|
+
<option value="other">Other</option>
|
|
54
|
+
</select-ui>
|
|
55
|
+
</field-ui>
|
|
56
|
+
<field-ui label="Industry — please specify" hint="Required when 'Other' is selected" required>
|
|
57
|
+
<input-ui name="industry_other" placeholder="e.g., Renewable energy"></input-ui>
|
|
58
|
+
</field-ui>
|
|
59
|
+
</col-ui>
|
|
60
|
+
</section>
|
|
61
|
+
</card-ui>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
</section>
|
|
65
|
+
|
|
66
|
+
<section data-section data-property="address-autocomplete">
|
|
67
|
+
<h2 variant="section">Address autocomplete</h2>
|
|
68
|
+
<p data-note>Single input that opens a `menu-ui` of suggestions as the user types. Each suggestion shows the formatted address with the matched portion highlighted. Selecting a suggestion fills the structured fields below; clicking "Enter manually" reveals the structured fields directly.</p>
|
|
69
|
+
<div data-artifact-container>
|
|
70
|
+
<div data-artifact-item data-artifact-label="address autocomplete">
|
|
71
|
+
<card-ui data-chunk="forms-address-autocomplete" style="max-width: 560px;">
|
|
72
|
+
<header>
|
|
73
|
+
<span slot="heading" variant="section">Billing address</span>
|
|
74
|
+
</header>
|
|
75
|
+
<section>
|
|
76
|
+
<col-ui gap="2">
|
|
77
|
+
<field-ui label="Search address" hint="Start typing — we'll suggest matches">
|
|
78
|
+
<input-ui name="address_query" placeholder="123 Main St" icon-leading="map-pin" value="500 market"></input-ui>
|
|
79
|
+
</field-ui>
|
|
80
|
+
<card-ui raw style="max-width: 100%;" data-shape="popover">
|
|
81
|
+
<section>
|
|
82
|
+
<col-ui gap="2">
|
|
83
|
+
<row-ui gap="2" align="center">
|
|
84
|
+
<icon-ui name="map-pin" size="sm"></icon-ui>
|
|
85
|
+
<col-ui gap="0" style="flex: 1;">
|
|
86
|
+
<text-ui><strong>500 Market</strong> Street</text-ui>
|
|
87
|
+
<text-ui color="subtle" size="sm">San Francisco, CA 94105 · USA</text-ui>
|
|
88
|
+
</col-ui>
|
|
89
|
+
</row-ui>
|
|
90
|
+
<row-ui gap="2" align="center">
|
|
91
|
+
<icon-ui name="map-pin" size="sm"></icon-ui>
|
|
92
|
+
<col-ui gap="0" style="flex: 1;">
|
|
93
|
+
<text-ui><strong>500 Market</strong> Place</text-ui>
|
|
94
|
+
<text-ui color="subtle" size="sm">Chattanooga, TN 37402 · USA</text-ui>
|
|
95
|
+
</col-ui>
|
|
96
|
+
</row-ui>
|
|
97
|
+
<row-ui gap="2" align="center">
|
|
98
|
+
<icon-ui name="map-pin" size="sm"></icon-ui>
|
|
99
|
+
<col-ui gap="0" style="flex: 1;">
|
|
100
|
+
<text-ui><strong>500 Market</strong> Square</text-ui>
|
|
101
|
+
<text-ui color="subtle" size="sm">Pittsburgh, PA 15222 · USA</text-ui>
|
|
102
|
+
</col-ui>
|
|
103
|
+
</row-ui>
|
|
104
|
+
<divider-ui></divider-ui>
|
|
105
|
+
<row-ui gap="2" align="center">
|
|
106
|
+
<icon-ui name="pencil-simple" size="sm"></icon-ui>
|
|
107
|
+
<text-ui style="flex: 1;">Enter address manually</text-ui>
|
|
108
|
+
</row-ui>
|
|
109
|
+
</col-ui>
|
|
110
|
+
</section>
|
|
111
|
+
</card-ui>
|
|
112
|
+
</col-ui>
|
|
113
|
+
</section>
|
|
114
|
+
</card-ui>
|
|
115
|
+
</div>
|
|
116
|
+
</div>
|
|
117
|
+
</section>
|
|
118
|
+
|
|
119
|
+
<section data-section data-property="address-fields">
|
|
120
|
+
<h2 variant="section">Resolved address — structured fields</h2>
|
|
121
|
+
<p data-note>After a suggestion is picked (or the user chose "Enter manually"), the structured fields appear, prefilled. The user can correct any line. Country drives the field set — US postal codes are different shape from CA, UK, etc.</p>
|
|
122
|
+
<div data-artifact-container>
|
|
123
|
+
<div data-artifact-item data-artifact-label="resolved address fields">
|
|
124
|
+
<card-ui data-chunk="forms-address-fields" style="max-width: 560px;">
|
|
125
|
+
<header>
|
|
126
|
+
<span slot="heading" variant="section">Address details</span>
|
|
127
|
+
<button-ui slot="action" text="Search again" variant="ghost" size="sm" icon-leading="arrow-counter-clockwise"></button-ui>
|
|
128
|
+
</header>
|
|
129
|
+
<section>
|
|
130
|
+
<col-ui gap="3">
|
|
131
|
+
<field-ui label="Street address" required>
|
|
132
|
+
<input-ui name="street1" value="500 Market Street"></input-ui>
|
|
133
|
+
</field-ui>
|
|
134
|
+
<field-ui label="Apt / Suite (optional)">
|
|
135
|
+
<input-ui name="street2" placeholder="Suite 200"></input-ui>
|
|
136
|
+
</field-ui>
|
|
137
|
+
<grid-ui columns="2" gap="3">
|
|
138
|
+
<field-ui label="City" required>
|
|
139
|
+
<input-ui name="city" value="San Francisco"></input-ui>
|
|
140
|
+
</field-ui>
|
|
141
|
+
<field-ui label="State" required>
|
|
142
|
+
<select-ui name="state">
|
|
143
|
+
<option value="CA" selected>California</option>
|
|
144
|
+
<option value="NY">New York</option>
|
|
145
|
+
<option value="TX">Texas</option>
|
|
146
|
+
</select-ui>
|
|
147
|
+
</field-ui>
|
|
148
|
+
</grid-ui>
|
|
149
|
+
<grid-ui columns="2" gap="3">
|
|
150
|
+
<field-ui label="ZIP code" required>
|
|
151
|
+
<input-ui name="postal_code" value="94105"></input-ui>
|
|
152
|
+
</field-ui>
|
|
153
|
+
<field-ui label="Country" required>
|
|
154
|
+
<select-ui name="country">
|
|
155
|
+
<option value="US" selected>United States</option>
|
|
156
|
+
<option value="CA">Canada</option>
|
|
157
|
+
<option value="GB">United Kingdom</option>
|
|
158
|
+
</select-ui>
|
|
159
|
+
</field-ui>
|
|
160
|
+
</grid-ui>
|
|
161
|
+
</col-ui>
|
|
162
|
+
</section>
|
|
163
|
+
</card-ui>
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
</section>
|
|
167
|
+
|
|
168
|
+
<section data-section data-property="composition">
|
|
169
|
+
<h2 variant="section">Composition rules</h2>
|
|
170
|
+
<ul>
|
|
171
|
+
<li><strong>Hidden fields are removed from the form, not just visually hidden</strong> — `display: none` still submits the value. Use `hidden` attr or detach from the DOM so a stale "Other — please specify" answer doesn't leak.</li>
|
|
172
|
+
<li><strong>Animate the reveal, focus the first new field</strong> — silent appearance is jarring. A height transition + focus call makes the form feel responsive without a separate page navigation.</li>
|
|
173
|
+
<li><strong>Country drives the address field set</strong> — US has state + ZIP; Canada has province + postal code; UK has county + postcode. A single "State / Province / Region" mega-label is worse than per-country layouts.</li>
|
|
174
|
+
<li><strong>Always allow manual entry</strong> — autocomplete services miss new addresses, edge cases, and apartments. The "Enter manually" escape hatch is mandatory.</li>
|
|
175
|
+
<li><strong>Highlight the matched portion in suggestions</strong> — `<strong>500 Market</strong> Street` reads faster than the unhighlighted full string. Use the user's query, not the API's match field, since they may diverge.</li>
|
|
176
|
+
<li><strong>Suggestions cap at 5</strong> — beyond that the picker becomes scroll-heavy. Refine the query, don't grow the menu.</li>
|
|
177
|
+
<li><strong>Required fields stay required when revealed</strong> — conditional reveal is a UX layer, not a constraint layer. The form-level validation should treat revealed required fields as required.</li>
|
|
178
|
+
</ul>
|
|
179
|
+
</section>
|
|
180
|
+
|
|
181
|
+
<section data-section data-property="cross-references">
|
|
182
|
+
<h2 variant="section">See also</h2>
|
|
183
|
+
<ul>
|
|
184
|
+
<li><a href="/site/components/field">field-ui</a> · <a href="/site/components/input">input-ui</a> · <a href="/site/components/select">select-ui</a> — form primitives.</li>
|
|
185
|
+
<li><a href="/site/components/menu">menu-ui</a> — autocomplete popover host.</li>
|
|
186
|
+
<li><a href="/site/patterns/form-drawer">form-drawer</a> — typical host for these surfaces.</li>
|
|
187
|
+
<li><a href="/site/patterns/wizard-drawer">wizard-drawer</a> — multi-step form host.</li>
|
|
188
|
+
</ul>
|
|
189
|
+
</section>
|