@ggui-ai/ui-visual-tester 0.19.0 → 0.21.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/dist/fixture-runtime.d.ts.map +1 -1
- package/dist/fixture-runtime.js +1 -150
- package/dist/prime-inputs.d.ts +12 -0
- package/dist/prime-inputs.d.ts.map +1 -0
- package/dist/prime-inputs.js +182 -0
- package/fixture/fixture-runtime.js +166 -126
- package/package.json +9 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fixture-runtime.d.ts","sourceRoot":"","sources":["../src/fixture-runtime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,KAAK,cAAc,MAAM,kBAAkB,CAAC;AACnD,OAAO,KAAK,gBAAgB,MAAM,4BAA4B,CAAC;AAC/D,OAAO,KAAK,gBAAgB,MAAM,4BAA4B,CAAC;AAC/D,OAAO,KAAK,kBAAkB,MAAM,8BAA8B,CAAC;AACnE,OAAO,KAAK,cAAc,MAAM,0BAA0B,CAAC;AAC3D,OAAO,KAAK,IAAI,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"fixture-runtime.d.ts","sourceRoot":"","sources":["../src/fixture-runtime.ts"],"names":[],"mappings":"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"}
|
package/dist/fixture-runtime.js
CHANGED
|
@@ -26,6 +26,7 @@ import * as Wire from '@ggui-ai/wire';
|
|
|
26
26
|
// in-repo consumers of the compile pipeline use.
|
|
27
27
|
import { hoistImports, loadModule } from '@ggui-ai/design/module-loader';
|
|
28
28
|
import { rewriteImports, stripMarkers, } from '@ggui-ai/design/rendering';
|
|
29
|
+
import { primeInputs } from './prime-inputs.js';
|
|
29
30
|
function parseActionEntry(raw) {
|
|
30
31
|
if (raw === null || typeof raw !== 'object')
|
|
31
32
|
return null;
|
|
@@ -150,156 +151,6 @@ function collectActionCandidates(root, name, label) {
|
|
|
150
151
|
}
|
|
151
152
|
return { named: [...byValue, ...byText], fallback: rest, disabled, namedDisabled, allClickables: every };
|
|
152
153
|
}
|
|
153
|
-
// #1040: date-like types too — a wizard's first step is routinely gated on one ("preferred follow-up
|
|
154
|
-
// date"), and a Next that never enables hides every step behind it from the probe.
|
|
155
|
-
const TEXT_LIKE_INPUT_TYPES = new Set([
|
|
156
|
-
'text', 'email', 'search', 'url', 'tel', 'number', 'password', '',
|
|
157
|
-
'date', 'time', 'month', 'week', 'datetime-local',
|
|
158
|
-
]);
|
|
159
|
-
/**
|
|
160
|
-
* #1040: dates are primed RELATIVE TO NOW — a form routinely refuses a date in the past
|
|
161
|
-
* ("preferred follow-up date"), and a constant sample date is a past date. Thirty days out,
|
|
162
|
-
* clamped into the input's own [min, max] when those are set in the input's format (ISO
|
|
163
|
-
* strings compare lexically). Time-of-day values have no past to fall into.
|
|
164
|
-
*/
|
|
165
|
-
const pad2 = (n) => String(n).padStart(2, '0');
|
|
166
|
-
const partsOf = (at) => ({ y: at.getFullYear(), m: at.getMonth() + 1, d: at.getDate() });
|
|
167
|
-
/** Today and thirty days out, as the parts a date-like value is built from. */
|
|
168
|
-
function todayAndThirtyDaysOut() {
|
|
169
|
-
const now = new Date();
|
|
170
|
-
const at = new Date(now);
|
|
171
|
-
at.setDate(at.getDate() + 30);
|
|
172
|
-
return { today: partsOf(now), later: partsOf(at) };
|
|
173
|
-
}
|
|
174
|
-
/**
|
|
175
|
-
* Clamp a value into the input's [min, max] when each is set in the input's own format
|
|
176
|
-
* (ISO-shaped strings compare lexically). `min` later than the value wins; `max` earlier than
|
|
177
|
-
* the value wins only while it is not before `floor` (today in the same format) — a max in
|
|
178
|
-
* the past is a form that can never be satisfied, and the probe reports that as it is.
|
|
179
|
-
*/
|
|
180
|
-
function clampToRange(el, value, floor, format) {
|
|
181
|
-
let out = value;
|
|
182
|
-
if (format.test(el.min) && el.min > out)
|
|
183
|
-
out = el.min;
|
|
184
|
-
if (format.test(el.max) && el.max < out && el.max >= floor)
|
|
185
|
-
out = el.max;
|
|
186
|
-
return out;
|
|
187
|
-
}
|
|
188
|
-
function isoWeekOf(y, m, d) {
|
|
189
|
-
// ISO-8601 week: Thursday of the same week decides the year.
|
|
190
|
-
const date = new Date(Date.UTC(y, m - 1, d));
|
|
191
|
-
const day = date.getUTCDay() || 7;
|
|
192
|
-
date.setUTCDate(date.getUTCDate() + 4 - day);
|
|
193
|
-
const jan1 = new Date(Date.UTC(date.getUTCFullYear(), 0, 1));
|
|
194
|
-
const week = Math.ceil(((date.getTime() - jan1.getTime()) / 86_400_000 + 1) / 7);
|
|
195
|
-
return `${date.getUTCFullYear()}-W${pad2(week)}`;
|
|
196
|
-
}
|
|
197
|
-
function sampleValueFor(el) {
|
|
198
|
-
if (!(el instanceof HTMLInputElement))
|
|
199
|
-
return 'probe'; // a textarea has no type to switch on
|
|
200
|
-
switch (el.type) {
|
|
201
|
-
case 'number':
|
|
202
|
-
return '1';
|
|
203
|
-
case 'email':
|
|
204
|
-
return 'probe@example.com';
|
|
205
|
-
case 'url':
|
|
206
|
-
return 'https://example.com';
|
|
207
|
-
case 'tel':
|
|
208
|
-
return '5550100';
|
|
209
|
-
case 'date': {
|
|
210
|
-
const { today, later } = todayAndThirtyDaysOut();
|
|
211
|
-
const iso = (p) => `${p.y}-${pad2(p.m)}-${pad2(p.d)}`;
|
|
212
|
-
return clampToRange(el, iso(later), iso(today), /^\d{4}-\d{2}-\d{2}$/);
|
|
213
|
-
}
|
|
214
|
-
case 'time':
|
|
215
|
-
return '09:30';
|
|
216
|
-
case 'month': {
|
|
217
|
-
const { today, later } = todayAndThirtyDaysOut();
|
|
218
|
-
const ym = (p) => `${p.y}-${pad2(p.m)}`;
|
|
219
|
-
return clampToRange(el, ym(later), ym(today), /^\d{4}-\d{2}$/);
|
|
220
|
-
}
|
|
221
|
-
case 'week': {
|
|
222
|
-
const { today, later } = todayAndThirtyDaysOut();
|
|
223
|
-
return clampToRange(el, isoWeekOf(later.y, later.m, later.d), isoWeekOf(today.y, today.m, today.d), /^\d{4}-W\d{2}$/);
|
|
224
|
-
}
|
|
225
|
-
case 'datetime-local': {
|
|
226
|
-
const { today, later } = todayAndThirtyDaysOut();
|
|
227
|
-
const at = (p, hm) => `${p.y}-${pad2(p.m)}-${pad2(p.d)}T${hm}`;
|
|
228
|
-
return clampToRange(el, at(later, '09:30'), at(today, '00:00'), /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/);
|
|
229
|
-
}
|
|
230
|
-
default:
|
|
231
|
-
return 'probe';
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
/** Set a value the way React notices it: through the prototype's native setter, then an input + change event. */
|
|
235
|
-
function setNativeValue(el, value) {
|
|
236
|
-
const proto = el instanceof HTMLTextAreaElement
|
|
237
|
-
? HTMLTextAreaElement.prototype
|
|
238
|
-
: el instanceof HTMLSelectElement
|
|
239
|
-
? HTMLSelectElement.prototype
|
|
240
|
-
: HTMLInputElement.prototype;
|
|
241
|
-
const setter = Object.getOwnPropertyDescriptor(proto, 'value')?.set;
|
|
242
|
-
if (setter)
|
|
243
|
-
setter.call(el, value);
|
|
244
|
-
else
|
|
245
|
-
el.value = value;
|
|
246
|
-
el.dispatchEvent(new Event('input', { bubbles: true }));
|
|
247
|
-
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
248
|
-
}
|
|
249
|
-
/**
|
|
250
|
-
* The generic minimum a user does before pressing Send / Submit / Complete
|
|
251
|
-
* (#1021): give every empty text-like input and textarea a value, every
|
|
252
|
-
* unset select its first real option, every unchecked radio group its first
|
|
253
|
-
* radio. Checkboxes are never touched — they are often the action itself.
|
|
254
|
-
* Returns how many controls were primed.
|
|
255
|
-
*/
|
|
256
|
-
function primeInputs(root) {
|
|
257
|
-
let primed = 0;
|
|
258
|
-
const editable = (el) => !el.disabled && !(el instanceof HTMLSelectElement) ? !el.readOnly : !el.disabled;
|
|
259
|
-
for (const el of Array.from(root.querySelectorAll('input, textarea'))) {
|
|
260
|
-
if (!(el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) || !editable(el))
|
|
261
|
-
continue;
|
|
262
|
-
if (el instanceof HTMLInputElement && !TEXT_LIKE_INPUT_TYPES.has(el.type))
|
|
263
|
-
continue;
|
|
264
|
-
if (el.value.trim().length > 0)
|
|
265
|
-
continue;
|
|
266
|
-
setNativeValue(el, sampleValueFor(el));
|
|
267
|
-
if (el.value.trim().length > 0)
|
|
268
|
-
primed += 1;
|
|
269
|
-
}
|
|
270
|
-
for (const el of Array.from(root.querySelectorAll('select'))) {
|
|
271
|
-
if (!(el instanceof HTMLSelectElement) || el.disabled)
|
|
272
|
-
continue;
|
|
273
|
-
const current = el.options[el.selectedIndex];
|
|
274
|
-
if (current !== undefined && current.value.trim().length > 0)
|
|
275
|
-
continue;
|
|
276
|
-
const first = Array.from(el.options).find((o) => o.value.trim().length > 0 && !o.disabled);
|
|
277
|
-
if (first === undefined)
|
|
278
|
-
continue;
|
|
279
|
-
setNativeValue(el, first.value);
|
|
280
|
-
primed += 1;
|
|
281
|
-
}
|
|
282
|
-
const seenGroups = new Set();
|
|
283
|
-
for (const el of Array.from(root.querySelectorAll('input[type="radio"]'))) {
|
|
284
|
-
if (!(el instanceof HTMLInputElement) || el.disabled)
|
|
285
|
-
continue;
|
|
286
|
-
const group = el.name || '';
|
|
287
|
-
if (seenGroups.has(group))
|
|
288
|
-
continue;
|
|
289
|
-
seenGroups.add(group);
|
|
290
|
-
const members = group
|
|
291
|
-
? Array.from(root.querySelectorAll(`input[type="radio"][name="${CSS.escape(group)}"]`)).filter((m) => m instanceof HTMLInputElement)
|
|
292
|
-
: [el];
|
|
293
|
-
if (members.some((m) => m.checked))
|
|
294
|
-
continue;
|
|
295
|
-
const first = members.find((m) => !m.disabled);
|
|
296
|
-
if (first === undefined)
|
|
297
|
-
continue;
|
|
298
|
-
first.click();
|
|
299
|
-
primed += 1;
|
|
300
|
-
}
|
|
301
|
-
return primed;
|
|
302
|
-
}
|
|
303
154
|
function countNamedDispatches(actionName) {
|
|
304
155
|
return (window.__ggui_test_dispatches__ ?? []).filter((d) => d.actionName === actionName).length;
|
|
305
156
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare function sampleValueFor(el: HTMLInputElement | HTMLTextAreaElement): string;
|
|
2
|
+
/** Set a value the way React notices it: through the prototype's native setter, then an input + change event. */
|
|
3
|
+
export declare function setNativeValue(el: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement, value: string): void;
|
|
4
|
+
/**
|
|
5
|
+
* The generic minimum a user does before pressing Send / Submit / Complete
|
|
6
|
+
* (#1021): give every empty text-like input and textarea a value, every
|
|
7
|
+
* unset select its first real option, every unchecked radio group its first
|
|
8
|
+
* radio. Checkboxes are never touched — they are often the action itself.
|
|
9
|
+
* Returns how many controls were primed.
|
|
10
|
+
*/
|
|
11
|
+
export declare function primeInputs(root: HTMLElement): number;
|
|
12
|
+
//# sourceMappingURL=prime-inputs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prime-inputs.d.ts","sourceRoot":"","sources":["../src/prime-inputs.ts"],"names":[],"mappings":"AA2EA,wBAAgB,cAAc,CAAC,EAAE,EAAE,gBAAgB,GAAG,mBAAmB,GAAG,MAAM,CAmCjF;AAED,iHAAiH;AACjH,wBAAgB,cAAc,CAAC,EAAE,EAAE,gBAAgB,GAAG,mBAAmB,GAAG,iBAAiB,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAYlH;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM,CA0CrD"}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
// oss/packages/ui-visual-tester/src/prime-inputs.ts
|
|
2
|
+
//
|
|
3
|
+
// Shared input-priming for the "generic minimum a user does before pressing
|
|
4
|
+
// Send / Submit / Complete" (#1021), incl. the #1040 relative-to-now date rule.
|
|
5
|
+
// Extracted from fixture-runtime.ts so BOTH the Playwright fixture runtime and
|
|
6
|
+
// the ui-gen runtime-render action-wiring probe (ggui#1187) prime inputs the
|
|
7
|
+
// SAME way — one typed-values table, no divergence. Pure DOM + Date only (no
|
|
8
|
+
// Playwright), so it is safe to import into the lightweight happy-dom check
|
|
9
|
+
// worker via the `@ggui-ai/ui-visual-tester/prime-inputs` subpath.
|
|
10
|
+
//
|
|
11
|
+
// Realm rule (ggui#1187 follow-up): every constructor this module needs —
|
|
12
|
+
// `HTMLInputElement`, `HTMLTextAreaElement`, `HTMLSelectElement`, `Event` —
|
|
13
|
+
// is read from the ROOT'S OWN window (`ownerDocument.defaultView`), never
|
|
14
|
+
// from ambient globals. The check worker installs happy-dom's `window` and
|
|
15
|
+
// `document` on `globalThis` without the element constructors, so an
|
|
16
|
+
// `instanceof HTMLInputElement` there is a `ReferenceError`, not a `false`;
|
|
17
|
+
// a browser page or vitest's happy-dom environment happens to have them.
|
|
18
|
+
// One code path, every realm.
|
|
19
|
+
/** The realm an element lives in. A detached document has none — that is a caller error, reported as one. */
|
|
20
|
+
function realmOf(el) {
|
|
21
|
+
const view = el.ownerDocument.defaultView;
|
|
22
|
+
if (view === null) {
|
|
23
|
+
throw new Error('primeInputs: the element is not attached to a window (ownerDocument.defaultView is null) — a detached document has no constructors to prime with');
|
|
24
|
+
}
|
|
25
|
+
return view;
|
|
26
|
+
}
|
|
27
|
+
// #1040: date-like types too — a wizard's first step is routinely gated on one ("preferred follow-up
|
|
28
|
+
// date"), and a Next that never enables hides every step behind it from the probe.
|
|
29
|
+
const TEXT_LIKE_INPUT_TYPES = new Set([
|
|
30
|
+
'text', 'email', 'search', 'url', 'tel', 'number', 'password', '',
|
|
31
|
+
'date', 'time', 'month', 'week', 'datetime-local',
|
|
32
|
+
]);
|
|
33
|
+
/**
|
|
34
|
+
* #1040: dates are primed RELATIVE TO NOW — a form routinely refuses a date in the past
|
|
35
|
+
* ("preferred follow-up date"), and a constant sample date is a past date. Thirty days out,
|
|
36
|
+
* clamped into the input's own [min, max] when those are set in the input's format (ISO
|
|
37
|
+
* strings compare lexically). Time-of-day values have no past to fall into.
|
|
38
|
+
*/
|
|
39
|
+
const pad2 = (n) => String(n).padStart(2, '0');
|
|
40
|
+
const partsOf = (at) => ({ y: at.getFullYear(), m: at.getMonth() + 1, d: at.getDate() });
|
|
41
|
+
/** Today and thirty days out, as the parts a date-like value is built from. */
|
|
42
|
+
function todayAndThirtyDaysOut() {
|
|
43
|
+
const now = new Date();
|
|
44
|
+
const at = new Date(now);
|
|
45
|
+
at.setDate(at.getDate() + 30);
|
|
46
|
+
return { today: partsOf(now), later: partsOf(at) };
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Clamp a value into the input's [min, max] when each is set in the input's own format
|
|
50
|
+
* (ISO-shaped strings compare lexically). `min` later than the value wins; `max` earlier than
|
|
51
|
+
* the value wins only while it is not before `floor` (today in the same format) — a max in
|
|
52
|
+
* the past is a form that can never be satisfied, and the probe reports that as it is.
|
|
53
|
+
*/
|
|
54
|
+
function clampToRange(el, value, floor, format) {
|
|
55
|
+
let out = value;
|
|
56
|
+
if (format.test(el.min) && el.min > out)
|
|
57
|
+
out = el.min;
|
|
58
|
+
if (format.test(el.max) && el.max < out && el.max >= floor)
|
|
59
|
+
out = el.max;
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
function isoWeekOf(y, m, d) {
|
|
63
|
+
// ISO-8601 week: Thursday of the same week decides the year.
|
|
64
|
+
const date = new Date(Date.UTC(y, m - 1, d));
|
|
65
|
+
const day = date.getUTCDay() || 7;
|
|
66
|
+
date.setUTCDate(date.getUTCDate() + 4 - day);
|
|
67
|
+
const jan1 = new Date(Date.UTC(date.getUTCFullYear(), 0, 1));
|
|
68
|
+
const week = Math.ceil(((date.getTime() - jan1.getTime()) / 86_400_000 + 1) / 7);
|
|
69
|
+
return `${date.getUTCFullYear()}-W${pad2(week)}`;
|
|
70
|
+
}
|
|
71
|
+
export function sampleValueFor(el) {
|
|
72
|
+
if (!(el instanceof realmOf(el).HTMLInputElement))
|
|
73
|
+
return 'probe'; // a textarea has no type to switch on
|
|
74
|
+
switch (el.type) {
|
|
75
|
+
case 'number':
|
|
76
|
+
return '1';
|
|
77
|
+
case 'email':
|
|
78
|
+
return 'probe@example.com';
|
|
79
|
+
case 'url':
|
|
80
|
+
return 'https://example.com';
|
|
81
|
+
case 'tel':
|
|
82
|
+
return '5550100';
|
|
83
|
+
case 'date': {
|
|
84
|
+
const { today, later } = todayAndThirtyDaysOut();
|
|
85
|
+
const iso = (p) => `${p.y}-${pad2(p.m)}-${pad2(p.d)}`;
|
|
86
|
+
return clampToRange(el, iso(later), iso(today), /^\d{4}-\d{2}-\d{2}$/);
|
|
87
|
+
}
|
|
88
|
+
case 'time':
|
|
89
|
+
return '09:30';
|
|
90
|
+
case 'month': {
|
|
91
|
+
const { today, later } = todayAndThirtyDaysOut();
|
|
92
|
+
const ym = (p) => `${p.y}-${pad2(p.m)}`;
|
|
93
|
+
return clampToRange(el, ym(later), ym(today), /^\d{4}-\d{2}$/);
|
|
94
|
+
}
|
|
95
|
+
case 'week': {
|
|
96
|
+
const { today, later } = todayAndThirtyDaysOut();
|
|
97
|
+
return clampToRange(el, isoWeekOf(later.y, later.m, later.d), isoWeekOf(today.y, today.m, today.d), /^\d{4}-W\d{2}$/);
|
|
98
|
+
}
|
|
99
|
+
case 'datetime-local': {
|
|
100
|
+
const { today, later } = todayAndThirtyDaysOut();
|
|
101
|
+
const at = (p, hm) => `${p.y}-${pad2(p.m)}-${pad2(p.d)}T${hm}`;
|
|
102
|
+
return clampToRange(el, at(later, '09:30'), at(today, '00:00'), /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/);
|
|
103
|
+
}
|
|
104
|
+
default:
|
|
105
|
+
return 'probe';
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/** Set a value the way React notices it: through the prototype's native setter, then an input + change event. */
|
|
109
|
+
export function setNativeValue(el, value) {
|
|
110
|
+
const view = realmOf(el);
|
|
111
|
+
const proto = el instanceof view.HTMLTextAreaElement
|
|
112
|
+
? view.HTMLTextAreaElement.prototype
|
|
113
|
+
: el instanceof view.HTMLSelectElement
|
|
114
|
+
? view.HTMLSelectElement.prototype
|
|
115
|
+
: view.HTMLInputElement.prototype;
|
|
116
|
+
const setter = Object.getOwnPropertyDescriptor(proto, 'value')?.set;
|
|
117
|
+
if (setter)
|
|
118
|
+
setter.call(el, value);
|
|
119
|
+
else
|
|
120
|
+
el.value = value;
|
|
121
|
+
el.dispatchEvent(new view.Event('input', { bubbles: true }));
|
|
122
|
+
el.dispatchEvent(new view.Event('change', { bubbles: true }));
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* The generic minimum a user does before pressing Send / Submit / Complete
|
|
126
|
+
* (#1021): give every empty text-like input and textarea a value, every
|
|
127
|
+
* unset select its first real option, every unchecked radio group its first
|
|
128
|
+
* radio. Checkboxes are never touched — they are often the action itself.
|
|
129
|
+
* Returns how many controls were primed.
|
|
130
|
+
*/
|
|
131
|
+
export function primeInputs(root) {
|
|
132
|
+
const view = realmOf(root);
|
|
133
|
+
let primed = 0;
|
|
134
|
+
const editable = (el) => !el.disabled && !el.readOnly;
|
|
135
|
+
for (const el of Array.from(root.querySelectorAll('input, textarea'))) {
|
|
136
|
+
if (!(el instanceof view.HTMLInputElement || el instanceof view.HTMLTextAreaElement) || !editable(el))
|
|
137
|
+
continue;
|
|
138
|
+
if (el instanceof view.HTMLInputElement && !TEXT_LIKE_INPUT_TYPES.has(el.type))
|
|
139
|
+
continue;
|
|
140
|
+
if (el.value.trim().length > 0)
|
|
141
|
+
continue;
|
|
142
|
+
setNativeValue(el, sampleValueFor(el));
|
|
143
|
+
if (el.value.trim().length > 0)
|
|
144
|
+
primed += 1;
|
|
145
|
+
}
|
|
146
|
+
for (const el of Array.from(root.querySelectorAll('select'))) {
|
|
147
|
+
if (!(el instanceof view.HTMLSelectElement) || el.disabled)
|
|
148
|
+
continue;
|
|
149
|
+
const current = el.options[el.selectedIndex];
|
|
150
|
+
if (current !== undefined && current.value.trim().length > 0)
|
|
151
|
+
continue;
|
|
152
|
+
const first = Array.from(el.options).find((o) => o.value.trim().length > 0 && !o.disabled);
|
|
153
|
+
if (first === undefined)
|
|
154
|
+
continue;
|
|
155
|
+
setNativeValue(el, first.value);
|
|
156
|
+
primed += 1;
|
|
157
|
+
}
|
|
158
|
+
// Radios: one pick per named group; an unnamed radio is its own group.
|
|
159
|
+
// Grouping is done in JS (by `name` equality), not by re-querying with the
|
|
160
|
+
// name interpolated into a selector — no `CSS.escape`, no realm dependency.
|
|
161
|
+
const radios = Array.from(root.querySelectorAll('input[type="radio"]')).filter((m) => m instanceof view.HTMLInputElement);
|
|
162
|
+
const seenGroups = new Set();
|
|
163
|
+
for (const el of radios) {
|
|
164
|
+
if (el.disabled)
|
|
165
|
+
continue;
|
|
166
|
+
const group = el.name;
|
|
167
|
+
if (group !== '') {
|
|
168
|
+
if (seenGroups.has(group))
|
|
169
|
+
continue;
|
|
170
|
+
seenGroups.add(group);
|
|
171
|
+
}
|
|
172
|
+
const members = group !== '' ? radios.filter((m) => m.name === group) : [el];
|
|
173
|
+
if (members.some((m) => m.checked))
|
|
174
|
+
continue;
|
|
175
|
+
const first = members.find((m) => !m.disabled);
|
|
176
|
+
if (first === undefined)
|
|
177
|
+
continue;
|
|
178
|
+
first.click();
|
|
179
|
+
primed += 1;
|
|
180
|
+
}
|
|
181
|
+
return primed;
|
|
182
|
+
}
|
|
@@ -20580,14 +20580,29 @@
|
|
|
20580
20580
|
easeInOut: "cubic-bezier(0.4, 0, 0.2, 1)",
|
|
20581
20581
|
spring: "cubic-bezier(0.175, 0.885, 0.32, 1.275)"
|
|
20582
20582
|
};
|
|
20583
|
+
var motionVar = {
|
|
20584
|
+
duration: {
|
|
20585
|
+
fast: "var(--ggui-motion-duration-fast)",
|
|
20586
|
+
base: "var(--ggui-motion-duration-base)",
|
|
20587
|
+
slow: "var(--ggui-motion-duration-slow)"
|
|
20588
|
+
},
|
|
20589
|
+
easing: {
|
|
20590
|
+
/** The system's ease-in-out — the default for colour, border and size changes. */
|
|
20591
|
+
standard: "var(--ggui-motion-easing-standard)",
|
|
20592
|
+
/** The decelerating arrival — transforms and entrances. */
|
|
20593
|
+
emphasized: "var(--ggui-motion-easing-emphasized)",
|
|
20594
|
+
/** The accelerating departure. */
|
|
20595
|
+
exit: "var(--ggui-motion-easing-exit)"
|
|
20596
|
+
}
|
|
20597
|
+
};
|
|
20583
20598
|
var transition = {
|
|
20584
20599
|
none: "none",
|
|
20585
|
-
fast: `${duration.fast} ${easing.
|
|
20586
|
-
normal: `${duration.
|
|
20587
|
-
slow: `${duration.slow} ${easing.
|
|
20588
|
-
colors: `color ${duration.
|
|
20589
|
-
opacity: `opacity ${duration.
|
|
20590
|
-
transform: `transform ${duration.
|
|
20600
|
+
fast: `${motionVar.duration.fast} ${motionVar.easing.standard}`,
|
|
20601
|
+
normal: `${motionVar.duration.base} ${motionVar.easing.standard}`,
|
|
20602
|
+
slow: `${motionVar.duration.slow} ${motionVar.easing.standard}`,
|
|
20603
|
+
colors: `color ${motionVar.duration.base} ${motionVar.easing.standard}, background-color ${motionVar.duration.base} ${motionVar.easing.standard}, border-color ${motionVar.duration.base} ${motionVar.easing.standard}`,
|
|
20604
|
+
opacity: `opacity ${motionVar.duration.base} ${motionVar.easing.standard}`,
|
|
20605
|
+
transform: `transform ${motionVar.duration.base} ${motionVar.easing.emphasized}`
|
|
20591
20606
|
};
|
|
20592
20607
|
|
|
20593
20608
|
// ../design/dist/primitives/Button.js
|
|
@@ -20663,7 +20678,7 @@
|
|
|
20663
20678
|
cursor: isDisabled ? "not-allowed" : "pointer",
|
|
20664
20679
|
opacity: isDisabled ? 0.5 : 1,
|
|
20665
20680
|
width: fullWidth ? "100%" : void 0,
|
|
20666
|
-
transition: `background-color ${duration.
|
|
20681
|
+
transition: `background-color ${motionVar.duration.base} ${motionVar.easing.standard}, box-shadow ${motionVar.duration.base} ${motionVar.easing.standard}, opacity ${motionVar.duration.base} ${motionVar.easing.standard}`,
|
|
20667
20682
|
boxShadow: "var(--ggui-shape-shadow-sm, 0 1px 2px rgba(0,0,0,0.05))",
|
|
20668
20683
|
...style
|
|
20669
20684
|
}, ...rest, children: loading ? (0, import_jsx_runtime8.jsx)(Spinner, { size: 16, tone: "inherit" }) : (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [leftIcon, children, rightIcon] }) });
|
|
@@ -20708,7 +20723,7 @@
|
|
|
20708
20723
|
border: `1px solid ${hasError ? "var(--ggui-color-error-500, #ef4444)" : "var(--ggui-color-outline, #d4d4d8)"}`,
|
|
20709
20724
|
backgroundColor: disabled ? "var(--ggui-color-sunken, #fafafa)" : "var(--ggui-color-sunken, #ffffff)",
|
|
20710
20725
|
color: "var(--ggui-color-onContainer, #18181b)",
|
|
20711
|
-
transition: `border-color ${duration.
|
|
20726
|
+
transition: `border-color ${motionVar.duration.base} ${motionVar.easing.standard}, box-shadow ${motionVar.duration.base} ${motionVar.easing.standard}`,
|
|
20712
20727
|
boxSizing: "border-box"
|
|
20713
20728
|
}, ...rest }), hasMessage && (0, import_jsx_runtime9.jsx)("span", { id: messageId, role: hasError ? "alert" : void 0, style: {
|
|
20714
20729
|
fontSize: "var(--ggui-font-size-xs, 12px)",
|
|
@@ -20744,7 +20759,7 @@
|
|
|
20744
20759
|
color: "var(--ggui-color-onContainer, #18181b)",
|
|
20745
20760
|
fontSize: "var(--ggui-font-size-sm, 14px)",
|
|
20746
20761
|
fontFamily: "inherit",
|
|
20747
|
-
transition: `border-color ${duration.
|
|
20762
|
+
transition: `border-color ${motionVar.duration.base} ${motionVar.easing.standard}, box-shadow ${motionVar.duration.base} ${motionVar.easing.standard}`,
|
|
20748
20763
|
boxSizing: "border-box",
|
|
20749
20764
|
resize: autoResize ? "none" : "vertical"
|
|
20750
20765
|
}, ...rest }), (0, import_jsx_runtime10.jsxs)("div", { style: { display: "flex", justifyContent: "space-between" }, children: [hasMessage && (0, import_jsx_runtime10.jsx)("span", { id: messageId, role: hasError ? "alert" : void 0, style: {
|
|
@@ -20789,7 +20804,7 @@
|
|
|
20789
20804
|
backgroundPosition: "right 12px center",
|
|
20790
20805
|
paddingRight: "36px",
|
|
20791
20806
|
boxSizing: "border-box",
|
|
20792
|
-
transition: `border-color ${duration.
|
|
20807
|
+
transition: `border-color ${motionVar.duration.base} ${motionVar.easing.standard}, box-shadow ${motionVar.duration.base} ${motionVar.easing.standard}`
|
|
20793
20808
|
}, ...rest, children: [placeholder && (0, import_jsx_runtime11.jsx)("option", { value: "", disabled: true, children: placeholder }), options.map((opt) => (0, import_jsx_runtime11.jsx)("option", { value: opt.value, disabled: opt.disabled, children: opt.label }, opt.value))] }), hasMessage && (0, import_jsx_runtime11.jsx)("span", { id: messageId, role: hasError ? "alert" : void 0, style: {
|
|
20794
20809
|
fontSize: "var(--ggui-font-size-xs, 12px)",
|
|
20795
20810
|
color: hasError ? "var(--ggui-color-error-500, #ef4444)" : "var(--ggui-color-onSunken, #52525b)"
|
|
@@ -20831,7 +20846,7 @@
|
|
|
20831
20846
|
display: "flex",
|
|
20832
20847
|
alignItems: "center",
|
|
20833
20848
|
justifyContent: "center",
|
|
20834
|
-
transition:
|
|
20849
|
+
transition: `all ${motionVar.duration.base} ${motionVar.easing.standard}`
|
|
20835
20850
|
}, children: (checked || indeterminate) && (0, import_jsx_runtime12.jsx)("svg", { width: "12", height: "12", viewBox: "0 0 12 12", fill: "none", style: { color: "var(--ggui-color-onPrimary, #ffffff)" }, children: indeterminate ? (0, import_jsx_runtime12.jsx)("path", { d: "M2.5 6H9.5", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" }) : (0, import_jsx_runtime12.jsx)("path", { d: "M2.5 6L5 8.5L9.5 3.5", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) }) })] }), (label || description) && (0, import_jsx_runtime12.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: "2px" }, children: [label && (0, import_jsx_runtime12.jsx)("span", { style: {
|
|
20836
20851
|
fontSize: "var(--ggui-font-size-sm, 14px)",
|
|
20837
20852
|
fontWeight: "var(--ggui-font-weight-medium, 500)",
|
|
@@ -20873,7 +20888,7 @@
|
|
|
20873
20888
|
borderRadius: "9999px",
|
|
20874
20889
|
backgroundColor: checked ? "var(--ggui-color-primary-600, #0284c7)" : "var(--ggui-color-outline, #d4d4d8)",
|
|
20875
20890
|
position: "relative",
|
|
20876
|
-
transition: `background-color ${duration.
|
|
20891
|
+
transition: `background-color ${motionVar.duration.base} ${motionVar.easing.standard}, box-shadow ${motionVar.duration.base} ${motionVar.easing.standard}`,
|
|
20877
20892
|
flexShrink: 0,
|
|
20878
20893
|
outline: "none",
|
|
20879
20894
|
boxShadow: focused ? "0 0 0 3px var(--ggui-color-primary-200, #bae6fd)" : void 0
|
|
@@ -20885,7 +20900,7 @@
|
|
|
20885
20900
|
position: "absolute",
|
|
20886
20901
|
top: (config.height - config.knob) / 2,
|
|
20887
20902
|
left: checked ? config.width - config.knob - (config.height - config.knob) / 2 : (config.height - config.knob) / 2,
|
|
20888
|
-
transition: `left ${duration.
|
|
20903
|
+
transition: `left ${motionVar.duration.base} ${motionVar.easing.standard}`,
|
|
20889
20904
|
boxShadow: "var(--ggui-shape-shadow-sm, 0 1px 2px rgba(0,0,0,0.05))"
|
|
20890
20905
|
} }) }), label && (0, import_jsx_runtime13.jsx)("span", { style: {
|
|
20891
20906
|
fontSize: "var(--ggui-font-size-sm, 14px)",
|
|
@@ -20943,7 +20958,7 @@
|
|
|
20943
20958
|
display: "flex",
|
|
20944
20959
|
alignItems: "center",
|
|
20945
20960
|
justifyContent: "center",
|
|
20946
|
-
transition:
|
|
20961
|
+
transition: `all ${motionVar.duration.base} ${motionVar.easing.standard}`
|
|
20947
20962
|
}, children: isSelected && (0, import_jsx_runtime14.jsx)("div", { style: {
|
|
20948
20963
|
width: "8px",
|
|
20949
20964
|
height: "8px",
|
|
@@ -20991,7 +21006,7 @@
|
|
|
20991
21006
|
height: "6px",
|
|
20992
21007
|
borderRadius: "3px",
|
|
20993
21008
|
backgroundColor: disabled ? "var(--ggui-color-outline, #d4d4d8)" : "var(--ggui-color-primary-600, #0284c7)",
|
|
20994
|
-
transition:
|
|
21009
|
+
transition: `width ${motionVar.duration.fast} ${motionVar.easing.standard}`
|
|
20995
21010
|
} }), (0, import_jsx_runtime15.jsx)("input", { id: sliderId, type: "range", min, max, step, value, onChange: (e) => onChange?.(Number(e.target.value)), disabled, "aria-labelledby": label ? labelId : void 0, "aria-label": label ? void 0 : "Slider", "aria-valuenow": value, "aria-valuemin": min, "aria-valuemax": max, style: {
|
|
20996
21011
|
position: "absolute",
|
|
20997
21012
|
width: "100%",
|
|
@@ -21009,7 +21024,7 @@
|
|
|
21009
21024
|
backgroundColor: "#ffffff",
|
|
21010
21025
|
border: `2px solid ${disabled ? "var(--ggui-color-outline, #d4d4d8)" : "var(--ggui-color-primary-600, #0284c7)"}`,
|
|
21011
21026
|
boxShadow: "var(--ggui-shape-shadow-sm, 0 1px 2px rgba(0,0,0,0.05))",
|
|
21012
|
-
transition:
|
|
21027
|
+
transition: `left ${motionVar.duration.fast} ${motionVar.easing.standard}`,
|
|
21013
21028
|
pointerEvents: "none"
|
|
21014
21029
|
} })] })] });
|
|
21015
21030
|
}
|
|
@@ -21237,7 +21252,7 @@
|
|
|
21237
21252
|
height: "100%",
|
|
21238
21253
|
borderRadius: height / 2,
|
|
21239
21254
|
backgroundColor: barColor,
|
|
21240
|
-
transition: indeterminate ? "none" :
|
|
21255
|
+
transition: indeterminate ? "none" : `width ${motionVar.duration.slow} ${motionVar.easing.standard}`,
|
|
21241
21256
|
...indeterminate ? {
|
|
21242
21257
|
width: "30%",
|
|
21243
21258
|
animation: "ggui-progress-indeterminate 1.5s ease-in-out infinite"
|
|
@@ -25869,7 +25884,7 @@
|
|
|
25869
25884
|
color: linkColor,
|
|
25870
25885
|
textDecoration: underlineStyle,
|
|
25871
25886
|
cursor: "pointer",
|
|
25872
|
-
transition:
|
|
25887
|
+
transition: `color ${motionVar.duration.base} ${motionVar.easing.standard}`,
|
|
25873
25888
|
...style
|
|
25874
25889
|
}, onMouseEnter: (e) => {
|
|
25875
25890
|
if (underline === "hover") {
|
|
@@ -26029,7 +26044,7 @@
|
|
|
26029
26044
|
cursor: col.sortable ? "pointer" : "default"
|
|
26030
26045
|
}, children: (0, import_jsx_runtime25.jsxs)("span", { style: { display: "inline-flex", alignItems: "center" }, children: [col.header, col.sortable && sortIndicator(sortKey === col.key, sortDirection)] }) }, col.key)) }) }), (0, import_jsx_runtime25.jsx)("tbody", { children: data.map((row, rowIndex) => (0, import_jsx_runtime25.jsx)("tr", { onMouseEnter: hoverable ? () => setHoveredRow(rowIndex) : void 0, onMouseLeave: hoverable ? () => setHoveredRow(null) : void 0, style: {
|
|
26031
26046
|
backgroundColor: hoverable && hoveredRow === rowIndex ? "var(--ggui-color-sunken, #fafafa)" : striped && rowIndex % 2 === 1 ? "var(--ggui-color-sunken, #fafafa)" : "transparent",
|
|
26032
|
-
transition:
|
|
26047
|
+
transition: `background-color ${motionVar.duration.fast} ${motionVar.easing.standard}`
|
|
26033
26048
|
}, children: columns.map((col) => (0, import_jsx_runtime25.jsx)("td", { style: {
|
|
26034
26049
|
padding: cellPadding,
|
|
26035
26050
|
textAlign: col.align || "left",
|
|
@@ -26103,7 +26118,7 @@
|
|
|
26103
26118
|
background: "none",
|
|
26104
26119
|
border: "none",
|
|
26105
26120
|
fontWeight: isActive ? "var(--ggui-font-weight-semibold, 600)" : "var(--ggui-font-weight-medium, 500)",
|
|
26106
|
-
transition: `color ${duration.
|
|
26121
|
+
transition: `color ${motionVar.duration.base} ${motionVar.easing.standard}, background-color ${motionVar.duration.base} ${motionVar.easing.standard}, border-color ${motionVar.duration.base} ${motionVar.easing.standard}`,
|
|
26107
26122
|
whiteSpace: "nowrap",
|
|
26108
26123
|
flex: fullWidth ? 1 : void 0,
|
|
26109
26124
|
justifyContent: fullWidth ? "center" : void 0
|
|
@@ -26397,7 +26412,7 @@
|
|
|
26397
26412
|
var import_jsx_runtime28 = __toESM(require_jsx_runtime(), 1);
|
|
26398
26413
|
var import_react18 = __toESM(require_react(), 1);
|
|
26399
26414
|
var chevron = (expanded) => (0, import_jsx_runtime28.jsx)("svg", { width: "16", height: "16", viewBox: "0 0 20 20", fill: "currentColor", "aria-hidden": "true", style: {
|
|
26400
|
-
transition: `transform ${duration.
|
|
26415
|
+
transition: `transform ${motionVar.duration.base} ${motionVar.easing.standard}`,
|
|
26401
26416
|
transform: expanded ? "rotate(180deg)" : "rotate(0deg)",
|
|
26402
26417
|
flexShrink: 0
|
|
26403
26418
|
}, children: (0, import_jsx_runtime28.jsx)("path", { fillRule: "evenodd", d: "M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z", clipRule: "evenodd" }) });
|
|
@@ -26470,7 +26485,7 @@
|
|
|
26470
26485
|
fontWeight: "var(--ggui-font-weight-medium, 500)",
|
|
26471
26486
|
color: "var(--ggui-color-onContainer, #18181b)",
|
|
26472
26487
|
textAlign: "left",
|
|
26473
|
-
transition: `background-color ${duration.fast} ${easing.
|
|
26488
|
+
transition: `background-color ${motionVar.duration.fast} ${motionVar.easing.standard}`
|
|
26474
26489
|
}, children: [(0, import_jsx_runtime28.jsx)("span", { children: item.title }), chevron(isExpanded)] }) }), (0, import_jsx_runtime28.jsx)("div", { id: `${uniqueId}-panel-${item.key}`, role: "region", "aria-labelledby": `${uniqueId}-header-${item.key}`, hidden: !isExpanded, style: {
|
|
26475
26490
|
overflow: "hidden"
|
|
26476
26491
|
}, children: isExpanded && (0, import_jsx_runtime28.jsx)("div", { style: {
|
|
@@ -26644,7 +26659,7 @@
|
|
|
26644
26659
|
fontWeight: active ? fontWeight.medium : fontWeight.normal,
|
|
26645
26660
|
textAlign: "left",
|
|
26646
26661
|
cursor: disabled ? "not-allowed" : "pointer",
|
|
26647
|
-
transition:
|
|
26662
|
+
transition: `background-color ${motionVar.duration.fast} ${motionVar.easing.standard}`,
|
|
26648
26663
|
...style
|
|
26649
26664
|
}, onMouseEnter: (e) => {
|
|
26650
26665
|
if (!disabled && !active) {
|
|
@@ -26962,7 +26977,7 @@
|
|
|
26962
26977
|
fontWeight: active ? fontWeight.medium : fontWeight.normal,
|
|
26963
26978
|
cursor: disabled ? "not-allowed" : "pointer",
|
|
26964
26979
|
opacity: disabled ? 0.5 : 1,
|
|
26965
|
-
transition:
|
|
26980
|
+
transition: `all ${motionVar.duration.fast} ${motionVar.easing.standard}`
|
|
26966
26981
|
}, children: page });
|
|
26967
26982
|
return (0, import_jsx_runtime36.jsxs)("nav", { "aria-label": "Pagination", className, style: {
|
|
26968
26983
|
display: "flex",
|
|
@@ -27030,7 +27045,7 @@
|
|
|
27030
27045
|
justifyContent: "center",
|
|
27031
27046
|
flexShrink: 0,
|
|
27032
27047
|
boxSizing: "border-box",
|
|
27033
|
-
transition:
|
|
27048
|
+
transition: `background-color ${motionVar.duration.base} ${motionVar.easing.standard}, border-color ${motionVar.duration.base} ${motionVar.easing.standard}, color ${motionVar.duration.base} ${motionVar.easing.standard}`,
|
|
27034
27049
|
...markerStyles[state]
|
|
27035
27050
|
}, children: state === "completed" ? (0, import_jsx_runtime39.jsx)(Icon, { name: "check", size: 16, tone: "inherit" }) : (0, import_jsx_runtime39.jsx)(Text, { is: "span", size: "sm", weight: "semibold", tone: "inherit", children: index + 1 }) });
|
|
27036
27051
|
}
|
|
@@ -29561,6 +29576,14 @@
|
|
|
29561
29576
|
"--ggui-font-weight-normal",
|
|
29562
29577
|
"--ggui-font-weight-semibold",
|
|
29563
29578
|
"--ggui-letter-spacing-heading",
|
|
29579
|
+
"--ggui-motion-duration-base",
|
|
29580
|
+
"--ggui-motion-duration-fast",
|
|
29581
|
+
"--ggui-motion-duration-slow",
|
|
29582
|
+
"--ggui-motion-easing-emphasized",
|
|
29583
|
+
"--ggui-motion-easing-exit",
|
|
29584
|
+
"--ggui-motion-easing-standard",
|
|
29585
|
+
"--ggui-scrim-opacity",
|
|
29586
|
+
"--ggui-scrim-tint",
|
|
29564
29587
|
"--ggui-shape-radius-control",
|
|
29565
29588
|
"--ggui-shape-radius-full",
|
|
29566
29589
|
"--ggui-shape-radius-lg",
|
|
@@ -32534,6 +32557,7 @@
|
|
|
32534
32557
|
"duration",
|
|
32535
32558
|
"easing",
|
|
32536
32559
|
"transition",
|
|
32560
|
+
"motionVar",
|
|
32537
32561
|
"keyframes",
|
|
32538
32562
|
"thinkingAnimation",
|
|
32539
32563
|
"thinkingKeyframes",
|
|
@@ -32794,94 +32818,13 @@
|
|
|
32794
32818
|
}
|
|
32795
32819
|
}
|
|
32796
32820
|
|
|
32797
|
-
// src/
|
|
32798
|
-
function
|
|
32799
|
-
|
|
32800
|
-
|
|
32801
|
-
|
|
32802
|
-
return { label: r.label };
|
|
32803
|
-
}
|
|
32804
|
-
function parseContract(raw) {
|
|
32805
|
-
const out = {};
|
|
32806
|
-
if (raw === null || typeof raw !== "object") return { actionSpec: out };
|
|
32807
|
-
const v = raw;
|
|
32808
|
-
if (v.actionSpec === null || typeof v.actionSpec !== "object") {
|
|
32809
|
-
return { actionSpec: out };
|
|
32810
|
-
}
|
|
32811
|
-
for (const [name, entry] of Object.entries(
|
|
32812
|
-
v.actionSpec
|
|
32813
|
-
)) {
|
|
32814
|
-
const parsed = parseActionEntry(entry);
|
|
32815
|
-
if (parsed !== null) out[name] = parsed;
|
|
32821
|
+
// src/prime-inputs.ts
|
|
32822
|
+
function realmOf(el) {
|
|
32823
|
+
const view = el.ownerDocument.defaultView;
|
|
32824
|
+
if (view === null) {
|
|
32825
|
+
throw new Error("primeInputs: the element is not attached to a window (ownerDocument.defaultView is null) \u2014 a detached document has no constructors to prime with");
|
|
32816
32826
|
}
|
|
32817
|
-
return
|
|
32818
|
-
}
|
|
32819
|
-
function installRegistry() {
|
|
32820
|
-
const registry = {
|
|
32821
|
-
react: React3,
|
|
32822
|
-
reactDom: ReactDomClient,
|
|
32823
|
-
primitives: primitives_exports,
|
|
32824
|
-
components: components_exports,
|
|
32825
|
-
compositions: compositions_exports,
|
|
32826
|
-
interact: interact_exports,
|
|
32827
|
-
appComponents: {},
|
|
32828
|
-
wire: dist_exports,
|
|
32829
|
-
adapters: {}
|
|
32830
|
-
};
|
|
32831
|
-
window.__ggui__ = registry;
|
|
32832
|
-
window.__REACT = React3;
|
|
32833
|
-
window.__GGUI_PRIMITIVES = primitives_exports;
|
|
32834
|
-
window.__GGUI_COMPONENTS = components_exports;
|
|
32835
|
-
window.__GGUI_COMPOSITIONS = compositions_exports;
|
|
32836
|
-
window.__GGUI_INTERACT = interact_exports;
|
|
32837
|
-
window.__GGUI_APP_COMPONENTS = registry.appComponents;
|
|
32838
|
-
return registry;
|
|
32839
|
-
}
|
|
32840
|
-
var CLICKABLE_SELECTOR = 'button, [role="button"], input[type="submit"], input[type="button"], input[type="checkbox"], input[type="radio"], [role="switch"], [role="checkbox"], [role="menuitem"], [role="tab"], [role="option"], [data-action]';
|
|
32841
|
-
var FALLBACK_CLICK_WAIT_MS = 600;
|
|
32842
|
-
var MAX_NAVIGATOR_RECLICKS = 8;
|
|
32843
|
-
var ACTION_BUDGET_FLOOR_MS = 6e3;
|
|
32844
|
-
function norm(text) {
|
|
32845
|
-
return text.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
32846
|
-
}
|
|
32847
|
-
function isControlDisabled(el) {
|
|
32848
|
-
return el.hasAttribute("disabled") || el.getAttribute("aria-disabled") === "true";
|
|
32849
|
-
}
|
|
32850
|
-
function collectActionCandidates(root, name, label) {
|
|
32851
|
-
const every = Array.from(root.querySelectorAll(CLICKABLE_SELECTOR)).filter(
|
|
32852
|
-
(el) => el instanceof HTMLElement
|
|
32853
|
-
);
|
|
32854
|
-
const isDisabled = isControlDisabled;
|
|
32855
|
-
const lcName = name.toLowerCase();
|
|
32856
|
-
const lcLabel = label.toLowerCase();
|
|
32857
|
-
const nameKey = norm(name);
|
|
32858
|
-
const labelKey = norm(label);
|
|
32859
|
-
const namesAction = (el) => {
|
|
32860
|
-
const dataAction = (el.getAttribute("data-action") ?? "").toLowerCase();
|
|
32861
|
-
if (dataAction === lcName) return "value";
|
|
32862
|
-
const aria = (el.getAttribute("aria-label") ?? "").toLowerCase();
|
|
32863
|
-
const txt = (el.textContent ?? "").trim().toLowerCase();
|
|
32864
|
-
const ariaNames = aria.length > 0 && (aria.includes(lcLabel) || nameKey.length > 0 && norm(aria).includes(nameKey));
|
|
32865
|
-
const textNames = txt.length > 0 && (txt.includes(lcLabel) || nameKey.length > 0 && norm(txt).includes(nameKey) || labelKey.length > 0 && norm(txt).includes(labelKey));
|
|
32866
|
-
return ariaNames || textNames ? "text" : null;
|
|
32867
|
-
};
|
|
32868
|
-
const disabledEls = every.filter(isDisabled);
|
|
32869
|
-
const disabled = disabledEls.length;
|
|
32870
|
-
const namedDisabled = disabledEls.filter((el) => namesAction(el) !== null).length;
|
|
32871
|
-
const all = every.filter(
|
|
32872
|
-
(el) => !isDisabled(el) && // an already-checked radio fires no change on click — nothing to observe
|
|
32873
|
-
!(el instanceof HTMLInputElement && el.type === "radio" && el.checked)
|
|
32874
|
-
);
|
|
32875
|
-
const byValue = [];
|
|
32876
|
-
const byText = [];
|
|
32877
|
-
const rest = [];
|
|
32878
|
-
for (const el of all) {
|
|
32879
|
-
const how = namesAction(el);
|
|
32880
|
-
if (how === "value") byValue.push(el);
|
|
32881
|
-
else if (how === "text") byText.push(el);
|
|
32882
|
-
else rest.push(el);
|
|
32883
|
-
}
|
|
32884
|
-
return { named: [...byValue, ...byText], fallback: rest, disabled, namedDisabled, allClickables: every };
|
|
32827
|
+
return view;
|
|
32885
32828
|
}
|
|
32886
32829
|
var TEXT_LIKE_INPUT_TYPES = /* @__PURE__ */ new Set([
|
|
32887
32830
|
"text",
|
|
@@ -32921,7 +32864,7 @@
|
|
|
32921
32864
|
return `${date.getUTCFullYear()}-W${pad2(week)}`;
|
|
32922
32865
|
}
|
|
32923
32866
|
function sampleValueFor(el) {
|
|
32924
|
-
if (!(el instanceof HTMLInputElement)) return "probe";
|
|
32867
|
+
if (!(el instanceof realmOf(el).HTMLInputElement)) return "probe";
|
|
32925
32868
|
switch (el.type) {
|
|
32926
32869
|
case "number":
|
|
32927
32870
|
return "1";
|
|
@@ -32957,25 +32900,27 @@
|
|
|
32957
32900
|
}
|
|
32958
32901
|
}
|
|
32959
32902
|
function setNativeValue(el, value) {
|
|
32960
|
-
const
|
|
32903
|
+
const view = realmOf(el);
|
|
32904
|
+
const proto = el instanceof view.HTMLTextAreaElement ? view.HTMLTextAreaElement.prototype : el instanceof view.HTMLSelectElement ? view.HTMLSelectElement.prototype : view.HTMLInputElement.prototype;
|
|
32961
32905
|
const setter = Object.getOwnPropertyDescriptor(proto, "value")?.set;
|
|
32962
32906
|
if (setter) setter.call(el, value);
|
|
32963
32907
|
else el.value = value;
|
|
32964
|
-
el.dispatchEvent(new Event("input", { bubbles: true }));
|
|
32965
|
-
el.dispatchEvent(new Event("change", { bubbles: true }));
|
|
32908
|
+
el.dispatchEvent(new view.Event("input", { bubbles: true }));
|
|
32909
|
+
el.dispatchEvent(new view.Event("change", { bubbles: true }));
|
|
32966
32910
|
}
|
|
32967
32911
|
function primeInputs(root) {
|
|
32912
|
+
const view = realmOf(root);
|
|
32968
32913
|
let primed = 0;
|
|
32969
|
-
const editable = (el) => !el.disabled && !
|
|
32914
|
+
const editable = (el) => !el.disabled && !el.readOnly;
|
|
32970
32915
|
for (const el of Array.from(root.querySelectorAll("input, textarea"))) {
|
|
32971
|
-
if (!(el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) || !editable(el)) continue;
|
|
32972
|
-
if (el instanceof HTMLInputElement && !TEXT_LIKE_INPUT_TYPES.has(el.type)) continue;
|
|
32916
|
+
if (!(el instanceof view.HTMLInputElement || el instanceof view.HTMLTextAreaElement) || !editable(el)) continue;
|
|
32917
|
+
if (el instanceof view.HTMLInputElement && !TEXT_LIKE_INPUT_TYPES.has(el.type)) continue;
|
|
32973
32918
|
if (el.value.trim().length > 0) continue;
|
|
32974
32919
|
setNativeValue(el, sampleValueFor(el));
|
|
32975
32920
|
if (el.value.trim().length > 0) primed += 1;
|
|
32976
32921
|
}
|
|
32977
32922
|
for (const el of Array.from(root.querySelectorAll("select"))) {
|
|
32978
|
-
if (!(el instanceof HTMLSelectElement) || el.disabled) continue;
|
|
32923
|
+
if (!(el instanceof view.HTMLSelectElement) || el.disabled) continue;
|
|
32979
32924
|
const current = el.options[el.selectedIndex];
|
|
32980
32925
|
if (current !== void 0 && current.value.trim().length > 0) continue;
|
|
32981
32926
|
const first = Array.from(el.options).find((o) => o.value.trim().length > 0 && !o.disabled);
|
|
@@ -32983,13 +32928,18 @@
|
|
|
32983
32928
|
setNativeValue(el, first.value);
|
|
32984
32929
|
primed += 1;
|
|
32985
32930
|
}
|
|
32931
|
+
const radios = Array.from(root.querySelectorAll('input[type="radio"]')).filter(
|
|
32932
|
+
(m) => m instanceof view.HTMLInputElement
|
|
32933
|
+
);
|
|
32986
32934
|
const seenGroups = /* @__PURE__ */ new Set();
|
|
32987
|
-
for (const el of
|
|
32988
|
-
if (
|
|
32989
|
-
const group = el.name
|
|
32990
|
-
if (
|
|
32991
|
-
|
|
32992
|
-
|
|
32935
|
+
for (const el of radios) {
|
|
32936
|
+
if (el.disabled) continue;
|
|
32937
|
+
const group = el.name;
|
|
32938
|
+
if (group !== "") {
|
|
32939
|
+
if (seenGroups.has(group)) continue;
|
|
32940
|
+
seenGroups.add(group);
|
|
32941
|
+
}
|
|
32942
|
+
const members = group !== "" ? radios.filter((m) => m.name === group) : [el];
|
|
32993
32943
|
if (members.some((m) => m.checked)) continue;
|
|
32994
32944
|
const first = members.find((m) => !m.disabled);
|
|
32995
32945
|
if (first === void 0) continue;
|
|
@@ -32998,6 +32948,96 @@
|
|
|
32998
32948
|
}
|
|
32999
32949
|
return primed;
|
|
33000
32950
|
}
|
|
32951
|
+
|
|
32952
|
+
// src/fixture-runtime.ts
|
|
32953
|
+
function parseActionEntry(raw) {
|
|
32954
|
+
if (raw === null || typeof raw !== "object") return null;
|
|
32955
|
+
const r = raw;
|
|
32956
|
+
if (typeof r.label !== "string") return null;
|
|
32957
|
+
return { label: r.label };
|
|
32958
|
+
}
|
|
32959
|
+
function parseContract(raw) {
|
|
32960
|
+
const out = {};
|
|
32961
|
+
if (raw === null || typeof raw !== "object") return { actionSpec: out };
|
|
32962
|
+
const v = raw;
|
|
32963
|
+
if (v.actionSpec === null || typeof v.actionSpec !== "object") {
|
|
32964
|
+
return { actionSpec: out };
|
|
32965
|
+
}
|
|
32966
|
+
for (const [name, entry] of Object.entries(
|
|
32967
|
+
v.actionSpec
|
|
32968
|
+
)) {
|
|
32969
|
+
const parsed = parseActionEntry(entry);
|
|
32970
|
+
if (parsed !== null) out[name] = parsed;
|
|
32971
|
+
}
|
|
32972
|
+
return { actionSpec: out };
|
|
32973
|
+
}
|
|
32974
|
+
function installRegistry() {
|
|
32975
|
+
const registry = {
|
|
32976
|
+
react: React3,
|
|
32977
|
+
reactDom: ReactDomClient,
|
|
32978
|
+
primitives: primitives_exports,
|
|
32979
|
+
components: components_exports,
|
|
32980
|
+
compositions: compositions_exports,
|
|
32981
|
+
interact: interact_exports,
|
|
32982
|
+
appComponents: {},
|
|
32983
|
+
wire: dist_exports,
|
|
32984
|
+
adapters: {}
|
|
32985
|
+
};
|
|
32986
|
+
window.__ggui__ = registry;
|
|
32987
|
+
window.__REACT = React3;
|
|
32988
|
+
window.__GGUI_PRIMITIVES = primitives_exports;
|
|
32989
|
+
window.__GGUI_COMPONENTS = components_exports;
|
|
32990
|
+
window.__GGUI_COMPOSITIONS = compositions_exports;
|
|
32991
|
+
window.__GGUI_INTERACT = interact_exports;
|
|
32992
|
+
window.__GGUI_APP_COMPONENTS = registry.appComponents;
|
|
32993
|
+
return registry;
|
|
32994
|
+
}
|
|
32995
|
+
var CLICKABLE_SELECTOR = 'button, [role="button"], input[type="submit"], input[type="button"], input[type="checkbox"], input[type="radio"], [role="switch"], [role="checkbox"], [role="menuitem"], [role="tab"], [role="option"], [data-action]';
|
|
32996
|
+
var FALLBACK_CLICK_WAIT_MS = 600;
|
|
32997
|
+
var MAX_NAVIGATOR_RECLICKS = 8;
|
|
32998
|
+
var ACTION_BUDGET_FLOOR_MS = 6e3;
|
|
32999
|
+
function norm(text) {
|
|
33000
|
+
return text.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
33001
|
+
}
|
|
33002
|
+
function isControlDisabled(el) {
|
|
33003
|
+
return el.hasAttribute("disabled") || el.getAttribute("aria-disabled") === "true";
|
|
33004
|
+
}
|
|
33005
|
+
function collectActionCandidates(root, name, label) {
|
|
33006
|
+
const every = Array.from(root.querySelectorAll(CLICKABLE_SELECTOR)).filter(
|
|
33007
|
+
(el) => el instanceof HTMLElement
|
|
33008
|
+
);
|
|
33009
|
+
const isDisabled = isControlDisabled;
|
|
33010
|
+
const lcName = name.toLowerCase();
|
|
33011
|
+
const lcLabel = label.toLowerCase();
|
|
33012
|
+
const nameKey = norm(name);
|
|
33013
|
+
const labelKey = norm(label);
|
|
33014
|
+
const namesAction = (el) => {
|
|
33015
|
+
const dataAction = (el.getAttribute("data-action") ?? "").toLowerCase();
|
|
33016
|
+
if (dataAction === lcName) return "value";
|
|
33017
|
+
const aria = (el.getAttribute("aria-label") ?? "").toLowerCase();
|
|
33018
|
+
const txt = (el.textContent ?? "").trim().toLowerCase();
|
|
33019
|
+
const ariaNames = aria.length > 0 && (aria.includes(lcLabel) || nameKey.length > 0 && norm(aria).includes(nameKey));
|
|
33020
|
+
const textNames = txt.length > 0 && (txt.includes(lcLabel) || nameKey.length > 0 && norm(txt).includes(nameKey) || labelKey.length > 0 && norm(txt).includes(labelKey));
|
|
33021
|
+
return ariaNames || textNames ? "text" : null;
|
|
33022
|
+
};
|
|
33023
|
+
const disabledEls = every.filter(isDisabled);
|
|
33024
|
+
const disabled = disabledEls.length;
|
|
33025
|
+
const namedDisabled = disabledEls.filter((el) => namesAction(el) !== null).length;
|
|
33026
|
+
const all = every.filter(
|
|
33027
|
+
(el) => !isDisabled(el) && // an already-checked radio fires no change on click — nothing to observe
|
|
33028
|
+
!(el instanceof HTMLInputElement && el.type === "radio" && el.checked)
|
|
33029
|
+
);
|
|
33030
|
+
const byValue = [];
|
|
33031
|
+
const byText = [];
|
|
33032
|
+
const rest = [];
|
|
33033
|
+
for (const el of all) {
|
|
33034
|
+
const how = namesAction(el);
|
|
33035
|
+
if (how === "value") byValue.push(el);
|
|
33036
|
+
else if (how === "text") byText.push(el);
|
|
33037
|
+
else rest.push(el);
|
|
33038
|
+
}
|
|
33039
|
+
return { named: [...byValue, ...byText], fallback: rest, disabled, namedDisabled, allClickables: every };
|
|
33040
|
+
}
|
|
33001
33041
|
function countNamedDispatches(actionName) {
|
|
33002
33042
|
return (window.__ggui_test_dispatches__ ?? []).filter((d) => d.actionName === actionName).length;
|
|
33003
33043
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ggui-ai/ui-visual-tester",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"description": "Behavioral validator for ggui-generated component code. Renders a component in real Chromium via Playwright, clicks each action declared in its contract, and verifies the action actually does something — mutating bound state or dispatching to the agent. Catches buttons wired to no-op handlers that structural validators miss. Playwright is an optional peer dependency, injected by the caller.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ggui",
|
|
@@ -35,6 +35,11 @@
|
|
|
35
35
|
"types": "./dist/index.d.ts",
|
|
36
36
|
"import": "./dist/index.js",
|
|
37
37
|
"default": "./dist/index.js"
|
|
38
|
+
},
|
|
39
|
+
"./prime-inputs": {
|
|
40
|
+
"types": "./dist/prime-inputs.d.ts",
|
|
41
|
+
"import": "./dist/prime-inputs.js",
|
|
42
|
+
"default": "./dist/prime-inputs.js"
|
|
38
43
|
}
|
|
39
44
|
},
|
|
40
45
|
"peerDependencies": {
|
|
@@ -48,9 +53,9 @@
|
|
|
48
53
|
"dependencies": {
|
|
49
54
|
"react": "^19.0.0",
|
|
50
55
|
"react-dom": "^19.0.0",
|
|
51
|
-
"@ggui-ai/
|
|
52
|
-
"@ggui-ai/
|
|
53
|
-
"@ggui-ai/
|
|
56
|
+
"@ggui-ai/protocol": "0.21.0",
|
|
57
|
+
"@ggui-ai/design": "0.21.0",
|
|
58
|
+
"@ggui-ai/wire": "0.21.0"
|
|
54
59
|
},
|
|
55
60
|
"devDependencies": {
|
|
56
61
|
"@playwright/test": "^1.60.0",
|