@epilot/zenith-react 0.2.0-next.4 → 0.2.0-next.6

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.
Files changed (185) hide show
  1. package/README.md +12 -1
  2. package/codemods/README.md +18 -4
  3. package/codemods/format.ts +86 -0
  4. package/codemods/migrate/from-base-elements.ts +8 -2
  5. package/codemods/migrate/from-volt.ts +8 -2
  6. package/codemods/radix-to-base-ui.ts +300 -12
  7. package/dist/_virtual/_zenith-runtime-family_accordion.js +1 -1
  8. package/dist/_virtual/_zenith-runtime-family_alert-dialog.js +1 -1
  9. package/dist/_virtual/_zenith-runtime-family_avatar.js +1 -1
  10. package/dist/_virtual/_zenith-runtime-family_button.js +1 -1
  11. package/dist/_virtual/_zenith-runtime-family_checkbox.js +1 -1
  12. package/dist/_virtual/_zenith-runtime-family_csp-provider.js +1 -1
  13. package/dist/_virtual/_zenith-runtime-family_dialog.js +1 -1
  14. package/dist/_virtual/_zenith-runtime-family_direction-provider.js +1 -1
  15. package/dist/_virtual/_zenith-runtime-family_menu.js +1 -1
  16. package/dist/_virtual/_zenith-runtime-family_merge-props.js +1 -1
  17. package/dist/_virtual/_zenith-runtime-family_popover.js +1 -1
  18. package/dist/_virtual/_zenith-runtime-family_radio-group.js +1 -1
  19. package/dist/_virtual/_zenith-runtime-family_radio.js +1 -1
  20. package/dist/_virtual/_zenith-runtime-family_scroll-area.js +1 -1
  21. package/dist/_virtual/_zenith-runtime-family_select.js +1 -1
  22. package/dist/_virtual/_zenith-runtime-family_separator.js +1 -1
  23. package/dist/_virtual/_zenith-runtime-family_switch.js +1 -1
  24. package/dist/_virtual/_zenith-runtime-family_tabs.js +1 -1
  25. package/dist/_virtual/_zenith-runtime-family_toast.js +1 -1
  26. package/dist/_virtual/_zenith-runtime-family_toggle-group.js +1 -1
  27. package/dist/_virtual/_zenith-runtime-family_toggle.js +1 -1
  28. package/dist/_virtual/_zenith-runtime-family_tooltip.js +1 -1
  29. package/dist/_virtual/_zenith-runtime-family_use-render.js +1 -1
  30. package/dist/components/accordion/accordion.js +1 -1
  31. package/dist/components/accordion/accordion.js.map +1 -1
  32. package/dist/components/alert-dialog/alert-dialog.js +1 -1
  33. package/dist/components/alert-dialog/alert-dialog.js.map +1 -1
  34. package/dist/components/appearance-boundary/appearance-boundary.js +1 -1
  35. package/dist/components/appearance-boundary/appearance-boundary.js.map +1 -1
  36. package/dist/components/avatar/avatar-group.js +1 -1
  37. package/dist/components/avatar/avatar-group.js.map +1 -1
  38. package/dist/components/avatar/avatar.js +1 -1
  39. package/dist/components/avatar/avatar.js.map +1 -1
  40. package/dist/components/badge/badge.js +1 -1
  41. package/dist/components/badge/badge.js.map +1 -1
  42. package/dist/components/breadcrumb/breadcrumb.js +1 -1
  43. package/dist/components/breadcrumb/breadcrumb.js.map +1 -1
  44. package/dist/components/button/button.js +1 -1
  45. package/dist/components/button/button.js.map +1 -1
  46. package/dist/components/button-group/button-group.js +1 -1
  47. package/dist/components/button-group/button-group.js.map +1 -1
  48. package/dist/components/callout/callout.js +1 -1
  49. package/dist/components/callout/callout.js.map +1 -1
  50. package/dist/components/card/card.js +1 -1
  51. package/dist/components/card/card.js.map +1 -1
  52. package/dist/components/checkbox/checkbox.js +1 -1
  53. package/dist/components/checkbox/checkbox.js.map +1 -1
  54. package/dist/components/code-block/code-block.js +1 -1
  55. package/dist/components/code-block/code-block.js.map +1 -1
  56. package/dist/components/code-editor/code-editor.js +1 -1
  57. package/dist/components/code-editor/code-editor.js.map +1 -1
  58. package/dist/components/collapsible-sidebar/collapsible-sidebar.js +1 -1
  59. package/dist/components/collapsible-sidebar/collapsible-sidebar.js.map +1 -1
  60. package/dist/components/command/command.js +1 -1
  61. package/dist/components/command/command.js.map +1 -1
  62. package/dist/components/data-table/data-table-body.js +1 -1
  63. package/dist/components/data-table/data-table-body.js.map +1 -1
  64. package/dist/components/data-table/data-table-column-header.js +1 -1
  65. package/dist/components/data-table/data-table-column-header.js.map +1 -1
  66. package/dist/components/data-table/data-table-column-order.js +1 -1
  67. package/dist/components/data-table/data-table-column-order.js.map +1 -1
  68. package/dist/components/data-table/data-table-column-visibility.js +1 -1
  69. package/dist/components/data-table/data-table-column-visibility.js.map +1 -1
  70. package/dist/components/data-table/data-table-content.js +1 -1
  71. package/dist/components/data-table/data-table-content.js.map +1 -1
  72. package/dist/components/data-table/data-table-error.js +1 -1
  73. package/dist/components/data-table/data-table-error.js.map +1 -1
  74. package/dist/components/data-table/data-table-footer.js +1 -1
  75. package/dist/components/data-table/data-table-footer.js.map +1 -1
  76. package/dist/components/data-table/data-table-header-draggable.js +1 -1
  77. package/dist/components/data-table/data-table-header-draggable.js.map +1 -1
  78. package/dist/components/data-table/data-table-header.js +1 -1
  79. package/dist/components/data-table/data-table-header.js.map +1 -1
  80. package/dist/components/data-table/data-table-loading.js +1 -1
  81. package/dist/components/data-table/data-table-loading.js.map +1 -1
  82. package/dist/components/data-table/data-table-resize-handle.js +1 -1
  83. package/dist/components/data-table/data-table-resize-handle.js.map +1 -1
  84. package/dist/components/data-table/data-table-row.js +1 -1
  85. package/dist/components/data-table/data-table-row.js.map +1 -1
  86. package/dist/components/data-table/data-table-toolbar.js +1 -1
  87. package/dist/components/data-table/data-table-toolbar.js.map +1 -1
  88. package/dist/components/data-table/data-table.js +1 -1
  89. package/dist/components/data-table/data-table.js.map +1 -1
  90. package/dist/components/date-range-picker/date-range-picker.js +1 -1
  91. package/dist/components/date-range-picker/date-range-picker.js.map +1 -1
  92. package/dist/components/date-range-picker/range-calendar.js +1 -1
  93. package/dist/components/date-range-picker/range-calendar.js.map +1 -1
  94. package/dist/components/date-time-picker/calendar.js +1 -1
  95. package/dist/components/date-time-picker/calendar.js.map +1 -1
  96. package/dist/components/date-time-picker/date-time-picker-input.js +1 -1
  97. package/dist/components/date-time-picker/date-time-picker-input.js.map +1 -1
  98. package/dist/components/date-time-picker/date-time-picker-panel.js +1 -1
  99. package/dist/components/date-time-picker/date-time-picker-panel.js.map +1 -1
  100. package/dist/components/date-time-picker/date-time-picker.js +1 -1
  101. package/dist/components/date-time-picker/date-time-picker.js.map +1 -1
  102. package/dist/components/date-time-picker/time-picker.js +1 -1
  103. package/dist/components/date-time-picker/time-picker.js.map +1 -1
  104. package/dist/components/density-provider/density-provider.js +1 -1
  105. package/dist/components/dialog/dialog.js +1 -1
  106. package/dist/components/dialog/dialog.js.map +1 -1
  107. package/dist/components/drawer/drawer.js +1 -1
  108. package/dist/components/drawer/drawer.js.map +1 -1
  109. package/dist/components/dropdown-menu/dropdown-menu.js +1 -1
  110. package/dist/components/dropdown-menu/dropdown-menu.js.map +1 -1
  111. package/dist/components/field/field-combobox.js +190 -181
  112. package/dist/components/field/field-combobox.js.map +1 -1
  113. package/dist/components/field/field-select.js +37 -22
  114. package/dist/components/field/field-select.js.map +1 -1
  115. package/dist/components/field/field.js +1 -1
  116. package/dist/components/field/field.js.map +1 -1
  117. package/dist/components/label/label.js +1 -1
  118. package/dist/components/label/label.js.map +1 -1
  119. package/dist/components/list/list.js +1 -1
  120. package/dist/components/list/list.js.map +1 -1
  121. package/dist/components/option-card/option-card.js +1 -1
  122. package/dist/components/option-card/option-card.js.map +1 -1
  123. package/dist/components/pill/pill.js +1 -1
  124. package/dist/components/pill/pill.js.map +1 -1
  125. package/dist/components/popover/popover.js +1 -1
  126. package/dist/components/popover/popover.js.map +1 -1
  127. package/dist/components/radio/radio.js +1 -1
  128. package/dist/components/radio/radio.js.map +1 -1
  129. package/dist/components/scroll-area/scroll-area.js +1 -1
  130. package/dist/components/scroll-area/scroll-area.js.map +1 -1
  131. package/dist/components/scroll-area/scrollbar-styles.js +1 -1
  132. package/dist/components/scroll-area/scrollbar-styles.js.map +1 -1
  133. package/dist/components/scroll-overflow/scroll-overflow.js +22 -22
  134. package/dist/components/scroll-overflow/scroll-overflow.js.map +1 -1
  135. package/dist/components/segmented-control/segmented-control.js +133 -122
  136. package/dist/components/segmented-control/segmented-control.js.map +1 -1
  137. package/dist/components/select/select.js +1 -1
  138. package/dist/components/select/select.js.map +1 -1
  139. package/dist/components/separator/separator.js +1 -1
  140. package/dist/components/separator/separator.js.map +1 -1
  141. package/dist/components/skeleton/skeleton.js +1 -1
  142. package/dist/components/skeleton/skeleton.js.map +1 -1
  143. package/dist/components/slot/compose-as-child.js +1 -1
  144. package/dist/components/slot/compose-as-child.js.map +1 -1
  145. package/dist/components/spinner/spinner.js +1 -1
  146. package/dist/components/spinner/spinner.js.map +1 -1
  147. package/dist/components/surface/surface.js +1 -1
  148. package/dist/components/surface/surface.js.map +1 -1
  149. package/dist/components/switch/switch.js +1 -1
  150. package/dist/components/switch/switch.js.map +1 -1
  151. package/dist/components/table/table-pagination.js +1 -1
  152. package/dist/components/table/table-pagination.js.map +1 -1
  153. package/dist/components/table/table.js +1 -1
  154. package/dist/components/table/table.js.map +1 -1
  155. package/dist/components/tabs/tabs.js +1 -1
  156. package/dist/components/tabs/tabs.js.map +1 -1
  157. package/dist/components/text/text.js +1 -1
  158. package/dist/components/text/text.js.map +1 -1
  159. package/dist/components/toast/toast.js +1 -1
  160. package/dist/components/toast/toast.js.map +1 -1
  161. package/dist/components/toggle/toggle-group.js +1 -1
  162. package/dist/components/toggle/toggle-group.js.map +1 -1
  163. package/dist/components/toggle/toggle.js +29 -26
  164. package/dist/components/toggle/toggle.js.map +1 -1
  165. package/dist/components/tooltip/tooltip.js +1 -1
  166. package/dist/components/tooltip/tooltip.js.map +1 -1
  167. package/dist/index.d.ts +1 -1
  168. package/dist/index.js +1 -1
  169. package/dist/lib/clear-button.js +1 -1
  170. package/dist/lib/clear-button.js.map +1 -1
  171. package/dist/packages/tokens/dist/delivery/index.js +1 -1
  172. package/dist/packages/tokens/dist/delivery/index.js.map +1 -1
  173. package/dist/packages/tokens/dist/install.js +1 -1
  174. package/dist/packages/tokens/dist/install.js.map +1 -1
  175. package/dist/renderer/served.js +19 -14
  176. package/dist/renderer/served.js.map +1 -1
  177. package/dist/styles/host-reset.js.map +1 -1
  178. package/dist/styles/make-styles.js +1 -1
  179. package/dist/styles/make-styles.js.map +1 -1
  180. package/dist/styles/text-box.js.map +1 -1
  181. package/dist/tokens.json +1 -1
  182. package/package.json +5 -3
  183. package/scripts/migration-diagnostics-core.mjs +284 -0
  184. package/scripts/migration-sweep.mjs +10 -10
  185. package/testing/setup.cjs +87 -0
@@ -29,6 +29,22 @@ export const RULES = [
29
29
  description:
30
30
  'A decorative (non-core) token family is used, with varying confidence that a correctly-scoped install call covers it (high confidence for no local evidence at all, down to low for an unresolved dynamic path). This rule NEVER reports severity "error": local absence of an install call does not prove a bootstrap/provider install does not exist elsewhere — this is single-source analysis with no cross-file tracing, so it cannot assert a definite defect, only flag what needs verification. Core families (accent/gray/error/warning/success/info) are excluded — they load by default. Requires an injected TypeScript module (see requiresTypeScript).',
31
31
  },
32
+ {
33
+ code: "radix-data-state-selector",
34
+ title: "Radix-era data-state selector or query",
35
+ defaultSeverity: "warning",
36
+ requiresTypeScript: false,
37
+ description:
38
+ "A `data-state` attribute selector, Tailwind `data-[state=…]` variant, JSX attribute or test query reads a state Radix set (`open`, `closed`, `checked`, `active`, `on`, …). The Base UI parts carry one attribute per state instead (`data-open`, `data-checked`, `data-active`, `data-pressed`, …), so the selector stops matching after the upgrade; the suggestion names the replacement per family (RADIX_RENAMES, the upgrade guide's table). A warning, not an error: Zenith's own List, Table and DataTable keep their `data-state` values, and a selector alone does not say which element it targets.",
39
+ },
40
+ {
41
+ code: "radix-css-variable",
42
+ title: "Radix-era --radix-* CSS variable",
43
+ defaultSeverity: "error",
44
+ requiresTypeScript: false,
45
+ description:
46
+ "A `--radix-*` custom property (`--radix-popover-content-transform-origin`, `--radix-select-trigger-width`, `--radix-accordion-content-height`, …) is read. Base UI publishes none of them; the positioner and panel variables have new names (`--transform-origin`, `--available-width`, `--anchor-width`, `--accordion-panel-height`, …), which the suggestion gives.",
47
+ },
32
48
  ]
33
49
 
34
50
  /** ts/tsx/js/jsx — the extensions `unestablished-palette-install` can parse. Exported so the
@@ -165,9 +181,277 @@ function textualFindings(identity, fileLabel, raw) {
165
181
  )
166
182
  }
167
183
 
184
+ findings.push(...radixFindings(identity, fileLabel, stripped, lineOf, colOf))
185
+ return findings
186
+ }
187
+
188
+ // ---------------------------------------------------------------------------
189
+ // Rule 4 + 5: Radix-era state attributes and CSS variables (textual, comment-stripped)
190
+ // ---------------------------------------------------------------------------
191
+
192
+ /** The Zenith parts an overlay-style `data-state="open|closed"` became `data-open`/`data-closed` on. */
193
+ const OPEN_CLOSED_PARTS = [
194
+ "Dialog",
195
+ "AlertDialog",
196
+ "Drawer",
197
+ "Popover",
198
+ "FieldCombobox",
199
+ "Tooltip",
200
+ "DropdownMenu content and sub content",
201
+ "Select content",
202
+ "Accordion item and panel",
203
+ ]
204
+ const POPUP_TRIGGERS = [
205
+ "Dialog trigger",
206
+ "AlertDialog trigger",
207
+ "Drawer trigger",
208
+ "Popover trigger",
209
+ "Tooltip trigger",
210
+ "DropdownMenu trigger and sub trigger",
211
+ "Select trigger",
212
+ ]
213
+ const CHECKED_PARTS = [
214
+ "Checkbox",
215
+ "Switch (root and thumb)",
216
+ "Radio",
217
+ "SegmentedControl",
218
+ "DropdownMenu checkbox and radio items",
219
+ ]
220
+
221
+ /** The Radix primitive names Zenith's positioned families used in their `--radix-*` variables. */
222
+ export const RADIX_VARIABLE_FAMILIES = [
223
+ { radix: "popover", zenith: "Popover, FieldCombobox" },
224
+ { radix: "dropdown-menu", zenith: "DropdownMenu" },
225
+ { radix: "select", zenith: "Select" },
226
+ { radix: "tooltip", zenith: "Tooltip" },
227
+ ]
228
+
229
+ /**
230
+ * The attribute and CSS-variable renames of the Base UI port (DECISIONS.md D12), one row per
231
+ * Radix name and Base UI replacement. The rules below match on these, and
232
+ * `renderRadixRenamesMarkdown()` prints the upgrade guide's table from them, so the two cannot
233
+ * drift. Family names are Zenith's; `<family>` in a variable name stands for one of
234
+ * RADIX_VARIABLE_FAMILIES. A `baseUi` of null means the state has no attribute any more: it is
235
+ * the part without the other one (`:not([data-active])`).
236
+ */
237
+ export const RADIX_RENAMES = {
238
+ attributes: [
239
+ { value: "open", baseUi: "data-open", parts: OPEN_CLOSED_PARTS },
240
+ { value: "open", baseUi: "data-popup-open", parts: POPUP_TRIGGERS },
241
+ { value: "open", baseUi: "data-panel-open", parts: ["Accordion trigger"] },
242
+ { value: "closed", baseUi: "data-closed", parts: OPEN_CLOSED_PARTS },
243
+ { value: "delayed-open", baseUi: "data-open", parts: ["Tooltip"] },
244
+ { value: "instant-open", baseUi: "data-open", parts: ["Tooltip"] },
245
+ { value: "checked", baseUi: "data-checked", parts: CHECKED_PARTS },
246
+ { value: "checked", baseUi: "data-selected", parts: ["Select item"] },
247
+ { value: "unchecked", baseUi: "data-unchecked", parts: CHECKED_PARTS },
248
+ {
249
+ value: "indeterminate",
250
+ baseUi: "data-indeterminate",
251
+ parts: ["Checkbox"],
252
+ },
253
+ { value: "active", baseUi: "data-active", parts: ["Tabs trigger"] },
254
+ {
255
+ value: "inactive",
256
+ baseUi: null,
257
+ parts: ["Tabs trigger"],
258
+ note: "the trigger without `data-active`",
259
+ },
260
+ { value: "on", baseUi: "data-pressed", parts: ["Toggle", "OptionCard"] },
261
+ {
262
+ value: "off",
263
+ baseUi: null,
264
+ parts: ["Toggle", "OptionCard"],
265
+ note: "the part without `data-pressed`",
266
+ },
267
+ ],
268
+ variables: [
269
+ {
270
+ radix: "--radix-<family>-content-transform-origin",
271
+ baseUi: "--transform-origin",
272
+ },
273
+ {
274
+ radix: "--radix-<family>-content-available-width",
275
+ baseUi: "--available-width",
276
+ },
277
+ {
278
+ radix: "--radix-<family>-content-available-height",
279
+ baseUi: "--available-height",
280
+ },
281
+ { radix: "--radix-<family>-trigger-width", baseUi: "--anchor-width" },
282
+ { radix: "--radix-<family>-trigger-height", baseUi: "--anchor-height" },
283
+ {
284
+ radix: "--radix-accordion-content-height",
285
+ baseUi: "--accordion-panel-height",
286
+ parts: ["Accordion panel"],
287
+ },
288
+ {
289
+ radix: "--radix-accordion-content-width",
290
+ baseUi: "--accordion-panel-width",
291
+ parts: ["Accordion panel"],
292
+ },
293
+ ],
294
+ /** Attributes the port left alone, for the table's footnote. */
295
+ unchanged: [
296
+ "data-highlighted",
297
+ "data-side",
298
+ "data-orientation",
299
+ "data-disabled",
300
+ "data-slot",
301
+ ],
302
+ /** New under Base UI: a part entering or leaving carries one of these while it transitions. */
303
+ added: ["data-starting-style", "data-ending-style"],
304
+ /** Zenith's own parts that still set `data-state`, with the values they use. */
305
+ keptDataState: [
306
+ { part: "List group", values: ["open", "closed"] },
307
+ { part: "Table row and DataTable row", values: ["selected"] },
308
+ { part: "DataTable body", values: ["loading", "error", "empty"] },
309
+ ],
310
+ }
311
+
312
+ const RADIX_STATE_VALUES = new Set(RADIX_RENAMES.attributes.map((a) => a.value))
313
+ /** `open` and `closed` are also Zenith's List values, so a hit on them is only probably Radix. */
314
+ const SHARED_STATE_VALUES = new Set(
315
+ RADIX_RENAMES.keptDataState
316
+ .flatMap((k) => k.values)
317
+ .filter((v) => RADIX_STATE_VALUES.has(v))
318
+ )
319
+
320
+ /**
321
+ * `data-state` wherever consumer code spells it: an attribute selector (`[data-state="open"]`,
322
+ * `[data-state=open]`, inside a CSS-in-JS string too), a JSX attribute, a Tailwind variant
323
+ * (`data-[state=open]:…`) and a test query (`toHaveAttribute("data-state", "open")`,
324
+ * `getAttribute("data-state")`). Group 1: the Tailwind value; 3: the selector or attribute
325
+ * value; 4: the query's value argument.
326
+ */
327
+ const DATA_STATE_RE =
328
+ /data-\[state=([a-z-]+)\]|\bdata-state\b(?![a-z0-9-])(?:\s*[~|^$*]?=\s*(["']?)([a-z-]+)\2|["']\s*,\s*["']([a-z-]+)["'])?/g
329
+ const RADIX_VAR_RE = /--radix-[a-z0-9-]+/g
330
+
331
+ const familyPattern = `(?<family>${RADIX_VARIABLE_FAMILIES.map((f) => f.radix).join("|")})`
332
+ const VARIABLE_MATCHERS = RADIX_RENAMES.variables.map((v) => ({
333
+ ...v,
334
+ re: new RegExp(`^${v.radix.replace("<family>", familyPattern)}$`),
335
+ }))
336
+
337
+ const describeParts = (parts) => parts.join(", ")
338
+
339
+ /** What replaces `data-state="<value>"`, per family, or null for a value Radix never set. */
340
+ export function dataStateReplacement(value) {
341
+ if (!value) {
342
+ return "one attribute per state on the Base UI part: data-open/data-closed (overlays, Accordion), data-popup-open (triggers), data-checked/data-unchecked (Checkbox, Switch, Radio, SegmentedControl, menu items), data-selected (Select item), data-active (Tabs), data-pressed (Toggle, OptionCard); see the upgrade guide's rename table."
343
+ }
344
+ const rows = RADIX_RENAMES.attributes.filter((a) => a.value === value)
345
+ if (!rows.length) return null
346
+ return rows
347
+ .map((r) =>
348
+ r.baseUi
349
+ ? `${r.baseUi} on ${describeParts(r.parts)}`
350
+ : `no attribute on ${describeParts(r.parts)} (${r.note})`
351
+ )
352
+ .join("; ")
353
+ }
354
+
355
+ /** The Base UI variable for a `--radix-*` name, or null when no Zenith family used it. */
356
+ export function radixVariableReplacement(name) {
357
+ for (const m of VARIABLE_MATCHERS) {
358
+ const hit = m.re.exec(name)
359
+ if (!hit) continue
360
+ const family = hit.groups?.family
361
+ const parts = family
362
+ ? RADIX_VARIABLE_FAMILIES.find((f) => f.radix === family).zenith
363
+ : describeParts(m.parts)
364
+ return { baseUi: m.baseUi, parts }
365
+ }
366
+ return null
367
+ }
368
+
369
+ function radixFindings(identity, fileLabel, stripped, lineOf, colOf) {
370
+ const findings = []
371
+ for (const m of stripped.matchAll(DATA_STATE_RE)) {
372
+ const value = m[1] ?? m[3] ?? m[4]
373
+ if (value && !RADIX_STATE_VALUES.has(value)) continue // Zenith's own values (selected, loading, …)
374
+ const replacement = dataStateReplacement(value)
375
+ findings.push(
376
+ makeFinding(identity, {
377
+ code: "radix-data-state-selector",
378
+ severity: "warning",
379
+ confidence:
380
+ !value || SHARED_STATE_VALUES.has(value) ? "medium" : "high",
381
+ file: fileLabel,
382
+ line: lineOf(m.index),
383
+ column: colOf(m.index),
384
+ evidence: m[0],
385
+ explanation: value
386
+ ? `Radix set data-state="${value}"; the Base UI part carries a dedicated attribute instead, so this selector or query matches nothing after the upgrade.${SHARED_STATE_VALUES.has(value) ? " Zenith's List still uses this value on its groups; leave the selector if it targets one." : ""}`
387
+ : "Radix set data-state on every stateful part; the Base UI parts carry one attribute per state instead. Zenith's List, Table and DataTable keep their own data-state values, so check what this reads.",
388
+ suggestion: `Use ${replacement}.`,
389
+ })
390
+ )
391
+ }
392
+ for (const m of stripped.matchAll(RADIX_VAR_RE)) {
393
+ const replacement = radixVariableReplacement(m[0])
394
+ findings.push(
395
+ makeFinding(identity, {
396
+ code: "radix-css-variable",
397
+ severity: "error",
398
+ confidence: "high",
399
+ file: fileLabel,
400
+ line: lineOf(m.index),
401
+ column: colOf(m.index),
402
+ evidence: m[0],
403
+ explanation:
404
+ "Base UI publishes no --radix-* custom property, so this resolves to nothing after the upgrade.",
405
+ suggestion: replacement
406
+ ? `Read ${replacement.baseUi} instead (${replacement.parts}).`
407
+ : "No Zenith family sets a Base UI counterpart for this variable; the declaration that reads it needs another source.",
408
+ })
409
+ )
410
+ }
168
411
  return findings
169
412
  }
170
413
 
414
+ /** The upgrade guide's rename table, from RADIX_RENAMES: one Markdown document with two tables. */
415
+ export function renderRadixRenamesMarkdown() {
416
+ const code = (s) => `\`${s}\``
417
+ const attributeRows = RADIX_RENAMES.attributes.map(
418
+ (a) =>
419
+ `| ${code(`data-state="${a.value}"`)} | ${a.baseUi ? code(a.baseUi) : `none (${a.note})`} | ${describeParts(a.parts)} |`
420
+ )
421
+ const variableRows = RADIX_RENAMES.variables.map((v) => {
422
+ const parts = v.parts
423
+ ? describeParts(v.parts)
424
+ : RADIX_VARIABLE_FAMILIES.map((f) => f.zenith).join(", ")
425
+ return `| ${code(v.radix)} | ${code(v.baseUi)} | ${parts} |`
426
+ })
427
+ const families = RADIX_VARIABLE_FAMILIES.map(
428
+ (f) => `${code(f.radix)} (${f.zenith})`
429
+ ).join(", ")
430
+ const kept = RADIX_RENAMES.keptDataState
431
+ .map((k) => `${k.part} (${k.values.map((v) => code(`"${v}"`)).join(", ")})`)
432
+ .join("; ")
433
+ return [
434
+ "<!-- Generated by packages/react/scripts/migration-renames.mjs from RADIX_RENAMES in migration-diagnostics-core.mjs. Do not edit; run `bun scripts/migration-renames.mjs --write`. -->",
435
+ "",
436
+ "### State attributes",
437
+ "",
438
+ "| Radix | Base UI | Zenith parts |",
439
+ "| --- | --- | --- |",
440
+ ...attributeRows,
441
+ "",
442
+ `${RADIX_RENAMES.added.map(code).join(" and ")} are new: a part carries one while it enters or leaves. ${RADIX_RENAMES.unchanged.map(code).join(", ")} and the ${code("aria-*")} attributes are unchanged. Zenith's own parts keep ${code("data-state")}: ${kept}.`,
443
+ "",
444
+ "### CSS variables",
445
+ "",
446
+ "| Radix | Base UI | Zenith parts |",
447
+ "| --- | --- | --- |",
448
+ ...variableRows,
449
+ "",
450
+ `${code("<family>")} is ${families}.`,
451
+ "",
452
+ ].join("\n")
453
+ }
454
+
171
455
  // ---------------------------------------------------------------------------
172
456
  // Rule 3: syntax-aware (TypeScript AST) palette-install analysis
173
457
  // ---------------------------------------------------------------------------
@@ -16,6 +16,14 @@ import {
16
16
  const DEFAULT_PACKAGE_DIR = resolve(import.meta.dirname, "..")
17
17
  const KNOWN_FLAGS = new Set(["--json", "--package", "--exclude"])
18
18
 
19
+ /** One finding: where and what, then the fix on its own line so the text report carries it too. */
20
+ function printFinding(f) {
21
+ console.log(
22
+ ` [${f.severity}] ${f.code.padEnd(28)} ${f.file}:${f.line}:${f.column} ${f.evidence}`
23
+ )
24
+ if (f.suggestion) console.log(` → ${f.suggestion}`)
25
+ }
26
+
19
27
  function usage() {
20
28
  return (
21
29
  "usage: node migration-sweep.mjs [--json] [--package <dir>] [--exclude <path>]... <path> [...]\n" +
@@ -157,11 +165,7 @@ export async function runCli(argv) {
157
165
  ? ` (${r.findings.length} finding(s) from the rules that did run, below)`
158
166
  : "")
159
167
  )
160
- for (const f of r.findings) {
161
- console.log(
162
- ` [${f.severity}] ${f.code.padEnd(28)} ${f.file}:${f.line}:${f.column} ${f.evidence}`
163
- )
164
- }
168
+ for (const f of r.findings) printFinding(f)
165
169
  continue
166
170
  }
167
171
  if (!r.findings.length) {
@@ -171,11 +175,7 @@ export async function runCli(argv) {
171
175
  continue
172
176
  }
173
177
  console.log(` ✗ ${name} — ${r.findings.length} finding(s)`)
174
- for (const f of r.findings) {
175
- console.log(
176
- ` [${f.severity}] ${f.code.padEnd(28)} ${f.file}:${f.line}:${f.column} ${f.evidence}`
177
- )
178
- }
178
+ for (const f of r.findings) printFinding(f)
179
179
  }
180
180
  console.log("")
181
181
  }
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Test setup for consumers of this package, for Vitest and Jest alike.
3
+ *
4
+ * WHY THIS EXISTS. Base UI, which sits under Switch, Checkbox, Radio and Select, re-dispatches a
5
+ * label's click as `new PointerEvent("click")`, the event type browsers use for clicks. jsdom
6
+ * added `PointerEvent` in 27; on an older jsdom (26 and below) the constructor does not exist, the
7
+ * dispatch throws, and the click never reaches the control. Nothing is wrong in the app.
8
+ *
9
+ * It installs a `PointerEvent` only where there is none. It mirrors Chromium's defaults for
10
+ * `new PointerEvent(type)`, and steps aside wherever the environment already has one, so it is
11
+ * safe to keep after a jsdom upgrade. It does nothing outside a DOM (a Node test environment).
12
+ *
13
+ * USAGE
14
+ *
15
+ * // vitest.config.ts
16
+ * export default defineConfig({
17
+ * test: {
18
+ * environment: "jsdom",
19
+ * setupFiles: ["@epilot/zenith-react/testing/setup.cjs"],
20
+ * },
21
+ * })
22
+ *
23
+ * // jest.config.cjs
24
+ * const griffel = require("@epilot/zenith-react/testing/jest-preset.cjs")
25
+ *
26
+ * module.exports = griffel.withGriffel(
27
+ * {
28
+ * testEnvironment: "jsdom",
29
+ * setupFiles: ["@epilot/zenith-react/testing/setup.cjs"],
30
+ * // Jest 28+ resolves package exports with the `require` condition, which
31
+ * // @epilot/zenith-react does not have: without the mapping the first import
32
+ * // fails with "Cannot find module".
33
+ * moduleNameMapper: {
34
+ * "^@epilot/zenith-react$":
35
+ * "<rootDir>/node_modules/@epilot/zenith-react/dist/index.js",
36
+ * "^@epilot/zenith-runtime/v1$":
37
+ * "<rootDir>/node_modules/@epilot/zenith-runtime/dist/v1/index.js",
38
+ * },
39
+ * },
40
+ * { alsoTransform: ["@epilot/zenith-runtime", "@base-ui", "@floating-ui"] }
41
+ * )
42
+ *
43
+ * Keep it in `setupFiles` (it runs before the test framework and the first import of a component);
44
+ * a setup run later still works, but a module that read `PointerEvent` at import time would not
45
+ * see it.
46
+ */
47
+ function installPointerEvent(scope) {
48
+ if (typeof scope.MouseEvent === "undefined") return false
49
+ if (typeof scope.PointerEvent !== "undefined") return false
50
+
51
+ class PointerEvent extends scope.MouseEvent {
52
+ constructor(type, init = {}) {
53
+ super(type, init)
54
+ this.pointerId = init.pointerId ?? 0
55
+ this.width = init.width ?? 1
56
+ this.height = init.height ?? 1
57
+ this.pressure = init.pressure ?? 0
58
+ this.tangentialPressure = init.tangentialPressure ?? 0
59
+ this.tiltX = init.tiltX ?? 0
60
+ this.tiltY = init.tiltY ?? 0
61
+ this.twist = init.twist ?? 0
62
+ this.pointerType = init.pointerType ?? ""
63
+ this.isPrimary = init.isPrimary ?? false
64
+ this._coalescedEvents = init.coalescedEvents ?? []
65
+ this._predictedEvents = init.predictedEvents ?? []
66
+ }
67
+
68
+ getCoalescedEvents() {
69
+ return [...this._coalescedEvents]
70
+ }
71
+
72
+ getPredictedEvents() {
73
+ return [...this._predictedEvents]
74
+ }
75
+ }
76
+
77
+ scope.PointerEvent = PointerEvent
78
+ // Vitest copies the jsdom window onto `globalThis` once; the class has to reach both.
79
+ if (typeof globalThis !== "undefined" && globalThis !== scope) {
80
+ globalThis.PointerEvent = PointerEvent
81
+ }
82
+ return true
83
+ }
84
+
85
+ if (typeof window !== "undefined") installPointerEvent(window)
86
+
87
+ module.exports = { installPointerEvent }