@ggui-ai/ui-visual-tester 0.20.0 → 0.22.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.
@@ -1 +1 @@
1
- {"version":3,"file":"prime-inputs.d.ts","sourceRoot":"","sources":["../src/prime-inputs.ts"],"names":[],"mappings":"AAsDA,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,CAWlH;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM,CAoCrD"}
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"}
@@ -7,6 +7,23 @@
7
7
  // SAME way — one typed-values table, no divergence. Pure DOM + Date only (no
8
8
  // Playwright), so it is safe to import into the lightweight happy-dom check
9
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
+ }
10
27
  // #1040: date-like types too — a wizard's first step is routinely gated on one ("preferred follow-up
11
28
  // date"), and a Next that never enables hides every step behind it from the probe.
12
29
  const TEXT_LIKE_INPUT_TYPES = new Set([
@@ -52,7 +69,7 @@ function isoWeekOf(y, m, d) {
52
69
  return `${date.getUTCFullYear()}-W${pad2(week)}`;
53
70
  }
54
71
  export function sampleValueFor(el) {
55
- if (!(el instanceof HTMLInputElement))
72
+ if (!(el instanceof realmOf(el).HTMLInputElement))
56
73
  return 'probe'; // a textarea has no type to switch on
57
74
  switch (el.type) {
58
75
  case 'number':
@@ -90,18 +107,19 @@ export function sampleValueFor(el) {
90
107
  }
91
108
  /** Set a value the way React notices it: through the prototype's native setter, then an input + change event. */
92
109
  export function setNativeValue(el, value) {
93
- const proto = el instanceof HTMLTextAreaElement
94
- ? HTMLTextAreaElement.prototype
95
- : el instanceof HTMLSelectElement
96
- ? HTMLSelectElement.prototype
97
- : HTMLInputElement.prototype;
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;
98
116
  const setter = Object.getOwnPropertyDescriptor(proto, 'value')?.set;
99
117
  if (setter)
100
118
  setter.call(el, value);
101
119
  else
102
120
  el.value = value;
103
- el.dispatchEvent(new Event('input', { bubbles: true }));
104
- el.dispatchEvent(new Event('change', { bubbles: true }));
121
+ el.dispatchEvent(new view.Event('input', { bubbles: true }));
122
+ el.dispatchEvent(new view.Event('change', { bubbles: true }));
105
123
  }
106
124
  /**
107
125
  * The generic minimum a user does before pressing Send / Submit / Complete
@@ -111,12 +129,13 @@ export function setNativeValue(el, value) {
111
129
  * Returns how many controls were primed.
112
130
  */
113
131
  export function primeInputs(root) {
132
+ const view = realmOf(root);
114
133
  let primed = 0;
115
- const editable = (el) => !el.disabled && !(el instanceof HTMLSelectElement) ? !el.readOnly : !el.disabled;
134
+ const editable = (el) => !el.disabled && !el.readOnly;
116
135
  for (const el of Array.from(root.querySelectorAll('input, textarea'))) {
117
- if (!(el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) || !editable(el))
136
+ if (!(el instanceof view.HTMLInputElement || el instanceof view.HTMLTextAreaElement) || !editable(el))
118
137
  continue;
119
- if (el instanceof HTMLInputElement && !TEXT_LIKE_INPUT_TYPES.has(el.type))
138
+ if (el instanceof view.HTMLInputElement && !TEXT_LIKE_INPUT_TYPES.has(el.type))
120
139
  continue;
121
140
  if (el.value.trim().length > 0)
122
141
  continue;
@@ -125,7 +144,7 @@ export function primeInputs(root) {
125
144
  primed += 1;
126
145
  }
127
146
  for (const el of Array.from(root.querySelectorAll('select'))) {
128
- if (!(el instanceof HTMLSelectElement) || el.disabled)
147
+ if (!(el instanceof view.HTMLSelectElement) || el.disabled)
129
148
  continue;
130
149
  const current = el.options[el.selectedIndex];
131
150
  if (current !== undefined && current.value.trim().length > 0)
@@ -136,17 +155,21 @@ export function primeInputs(root) {
136
155
  setNativeValue(el, first.value);
137
156
  primed += 1;
138
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);
139
162
  const seenGroups = new Set();
140
- for (const el of Array.from(root.querySelectorAll('input[type="radio"]'))) {
141
- if (!(el instanceof HTMLInputElement) || el.disabled)
163
+ for (const el of radios) {
164
+ if (el.disabled)
142
165
  continue;
143
- const group = el.name || '';
144
- if (seenGroups.has(group))
145
- continue;
146
- seenGroups.add(group);
147
- const members = group
148
- ? Array.from(root.querySelectorAll(`input[type="radio"][name="${CSS.escape(group)}"]`)).filter((m) => m instanceof HTMLInputElement)
149
- : [el];
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];
150
173
  if (members.some((m) => m.checked))
151
174
  continue;
152
175
  const first = members.find((m) => !m.disabled);
@@ -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.easeInOut}`,
20586
- normal: `${duration.normal} ${easing.easeInOut}`,
20587
- slow: `${duration.slow} ${easing.easeInOut}`,
20588
- colors: `color ${duration.normal} ${easing.easeInOut}, background-color ${duration.normal} ${easing.easeInOut}, border-color ${duration.normal} ${easing.easeInOut}`,
20589
- opacity: `opacity ${duration.normal} ${easing.easeInOut}`,
20590
- transform: `transform ${duration.normal} ${easing.easeOut}`
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.normal} ${easing.easeInOut}, box-shadow ${duration.normal} ${easing.easeInOut}, opacity ${duration.normal} ${easing.easeInOut}`,
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.normal} ${easing.easeInOut}, box-shadow ${duration.normal} ${easing.easeInOut}`,
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.normal} ${easing.easeInOut}, box-shadow ${duration.normal} ${easing.easeInOut}`,
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.normal} ${easing.easeInOut}, box-shadow ${duration.normal} ${easing.easeInOut}`
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: "all 0.2s"
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.normal} ${easing.easeInOut}, box-shadow ${duration.normal} ${easing.easeInOut}`,
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.normal} ${easing.easeInOut}`,
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: "all 0.2s"
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: "width 0.1s"
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: "left 0.1s",
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" : "width 0.3s ease",
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: "color 0.2s",
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: "background-color 0.15s ease"
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.normal} ${easing.easeInOut}, background-color ${duration.normal} ${easing.easeInOut}, border-color ${duration.normal} ${easing.easeInOut}`,
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.normal} ${easing.easeInOut}`,
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.easeInOut}`
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: "background-color 0.15s",
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: "all 0.15s"
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: "background-color 0.2s, border-color 0.2s, color 0.2s",
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
  }
@@ -29381,6 +29396,8 @@
29381
29396
  ...opts.onDispatchSuppressed !== void 0 ? { onDispatchSuppressed: opts.onDispatchSuppressed } : {},
29382
29397
  dispatch: (actionName, data) => {
29383
29398
  const actionSpec = opts.getActiveActionSpec();
29399
+ if (actionSpec === void 0)
29400
+ opts.onActionSpecAbsent?.(actionName);
29384
29401
  const isOneShot = actionSpec?.[actionName]?.oneShot === true;
29385
29402
  if (isOneShot && spentOneShots.has(actionName)) {
29386
29403
  console.warn(`[ggui] dispatch('${actionName}') suppressed: this action is declared oneShot and already fired for this render. A second gesture is NOT sent to the agent \u2014 the submitted control is spent for the render's lifetime. If the action is meant to repeat, remove \`oneShot\` from its contract entry.`);
@@ -29561,6 +29578,14 @@
29561
29578
  "--ggui-font-weight-normal",
29562
29579
  "--ggui-font-weight-semibold",
29563
29580
  "--ggui-letter-spacing-heading",
29581
+ "--ggui-motion-duration-base",
29582
+ "--ggui-motion-duration-fast",
29583
+ "--ggui-motion-duration-slow",
29584
+ "--ggui-motion-easing-emphasized",
29585
+ "--ggui-motion-easing-exit",
29586
+ "--ggui-motion-easing-standard",
29587
+ "--ggui-scrim-opacity",
29588
+ "--ggui-scrim-tint",
29564
29589
  "--ggui-shape-radius-control",
29565
29590
  "--ggui-shape-radius-full",
29566
29591
  "--ggui-shape-radius-lg",
@@ -32534,6 +32559,7 @@
32534
32559
  "duration",
32535
32560
  "easing",
32536
32561
  "transition",
32562
+ "motionVar",
32537
32563
  "keyframes",
32538
32564
  "thinkingAnimation",
32539
32565
  "thinkingKeyframes",
@@ -32795,6 +32821,13 @@
32795
32821
  }
32796
32822
 
32797
32823
  // src/prime-inputs.ts
32824
+ function realmOf(el) {
32825
+ const view = el.ownerDocument.defaultView;
32826
+ if (view === null) {
32827
+ 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");
32828
+ }
32829
+ return view;
32830
+ }
32798
32831
  var TEXT_LIKE_INPUT_TYPES = /* @__PURE__ */ new Set([
32799
32832
  "text",
32800
32833
  "email",
@@ -32833,7 +32866,7 @@
32833
32866
  return `${date.getUTCFullYear()}-W${pad2(week)}`;
32834
32867
  }
32835
32868
  function sampleValueFor(el) {
32836
- if (!(el instanceof HTMLInputElement)) return "probe";
32869
+ if (!(el instanceof realmOf(el).HTMLInputElement)) return "probe";
32837
32870
  switch (el.type) {
32838
32871
  case "number":
32839
32872
  return "1";
@@ -32869,25 +32902,27 @@
32869
32902
  }
32870
32903
  }
32871
32904
  function setNativeValue(el, value) {
32872
- const proto = el instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : el instanceof HTMLSelectElement ? HTMLSelectElement.prototype : HTMLInputElement.prototype;
32905
+ const view = realmOf(el);
32906
+ const proto = el instanceof view.HTMLTextAreaElement ? view.HTMLTextAreaElement.prototype : el instanceof view.HTMLSelectElement ? view.HTMLSelectElement.prototype : view.HTMLInputElement.prototype;
32873
32907
  const setter = Object.getOwnPropertyDescriptor(proto, "value")?.set;
32874
32908
  if (setter) setter.call(el, value);
32875
32909
  else el.value = value;
32876
- el.dispatchEvent(new Event("input", { bubbles: true }));
32877
- el.dispatchEvent(new Event("change", { bubbles: true }));
32910
+ el.dispatchEvent(new view.Event("input", { bubbles: true }));
32911
+ el.dispatchEvent(new view.Event("change", { bubbles: true }));
32878
32912
  }
32879
32913
  function primeInputs(root) {
32914
+ const view = realmOf(root);
32880
32915
  let primed = 0;
32881
- const editable = (el) => !el.disabled && !(el instanceof HTMLSelectElement) ? !el.readOnly : !el.disabled;
32916
+ const editable = (el) => !el.disabled && !el.readOnly;
32882
32917
  for (const el of Array.from(root.querySelectorAll("input, textarea"))) {
32883
- if (!(el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) || !editable(el)) continue;
32884
- if (el instanceof HTMLInputElement && !TEXT_LIKE_INPUT_TYPES.has(el.type)) continue;
32918
+ if (!(el instanceof view.HTMLInputElement || el instanceof view.HTMLTextAreaElement) || !editable(el)) continue;
32919
+ if (el instanceof view.HTMLInputElement && !TEXT_LIKE_INPUT_TYPES.has(el.type)) continue;
32885
32920
  if (el.value.trim().length > 0) continue;
32886
32921
  setNativeValue(el, sampleValueFor(el));
32887
32922
  if (el.value.trim().length > 0) primed += 1;
32888
32923
  }
32889
32924
  for (const el of Array.from(root.querySelectorAll("select"))) {
32890
- if (!(el instanceof HTMLSelectElement) || el.disabled) continue;
32925
+ if (!(el instanceof view.HTMLSelectElement) || el.disabled) continue;
32891
32926
  const current = el.options[el.selectedIndex];
32892
32927
  if (current !== void 0 && current.value.trim().length > 0) continue;
32893
32928
  const first = Array.from(el.options).find((o) => o.value.trim().length > 0 && !o.disabled);
@@ -32895,13 +32930,18 @@
32895
32930
  setNativeValue(el, first.value);
32896
32931
  primed += 1;
32897
32932
  }
32933
+ const radios = Array.from(root.querySelectorAll('input[type="radio"]')).filter(
32934
+ (m) => m instanceof view.HTMLInputElement
32935
+ );
32898
32936
  const seenGroups = /* @__PURE__ */ new Set();
32899
- for (const el of Array.from(root.querySelectorAll('input[type="radio"]'))) {
32900
- if (!(el instanceof HTMLInputElement) || el.disabled) continue;
32901
- const group = el.name || "";
32902
- if (seenGroups.has(group)) continue;
32903
- seenGroups.add(group);
32904
- const members = group ? Array.from(root.querySelectorAll(`input[type="radio"][name="${CSS.escape(group)}"]`)).filter((m) => m instanceof HTMLInputElement) : [el];
32937
+ for (const el of radios) {
32938
+ if (el.disabled) continue;
32939
+ const group = el.name;
32940
+ if (group !== "") {
32941
+ if (seenGroups.has(group)) continue;
32942
+ seenGroups.add(group);
32943
+ }
32944
+ const members = group !== "" ? radios.filter((m) => m.name === group) : [el];
32905
32945
  if (members.some((m) => m.checked)) continue;
32906
32946
  const first = members.find((m) => !m.disabled);
32907
32947
  if (first === void 0) continue;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ggui-ai/ui-visual-tester",
3
- "version": "0.20.0",
3
+ "version": "0.22.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",
@@ -53,9 +53,9 @@
53
53
  "dependencies": {
54
54
  "react": "^19.0.0",
55
55
  "react-dom": "^19.0.0",
56
- "@ggui-ai/design": "0.20.0",
57
- "@ggui-ai/protocol": "0.20.0",
58
- "@ggui-ai/wire": "0.20.0"
56
+ "@ggui-ai/design": "0.22.0",
57
+ "@ggui-ai/protocol": "0.22.0",
58
+ "@ggui-ai/wire": "0.22.0"
59
59
  },
60
60
  "devDependencies": {
61
61
  "@playwright/test": "^1.60.0",