bestax-migrate 2.1.6 → 2.2.1

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 (64) hide show
  1. package/README.md +13 -8
  2. package/dist/cli.d.ts +53 -0
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cli.js +159 -9
  5. package/dist/sources/_shared/imports.d.ts +46 -0
  6. package/dist/sources/_shared/imports.d.ts.map +1 -0
  7. package/dist/sources/_shared/imports.js +152 -0
  8. package/dist/sources/{react-bulma-components → _shared}/jsx-utils.d.ts +20 -12
  9. package/dist/sources/_shared/jsx-utils.d.ts.map +1 -0
  10. package/dist/sources/{react-bulma-components → _shared}/jsx-utils.js +15 -10
  11. package/dist/sources/_shared/make-styles-transform.d.ts +37 -0
  12. package/dist/sources/_shared/make-styles-transform.d.ts.map +1 -0
  13. package/dist/sources/_shared/make-styles-transform.js +440 -0
  14. package/dist/sources/_shared/props.d.ts +20 -0
  15. package/dist/sources/_shared/props.d.ts.map +1 -0
  16. package/dist/sources/{react-bulma-components → _shared}/props.js +11 -7
  17. package/dist/sources/_shared/semver-range.d.ts +31 -0
  18. package/dist/sources/_shared/semver-range.d.ts.map +1 -0
  19. package/dist/sources/_shared/semver-range.js +186 -0
  20. package/dist/sources/_shared/specials-utils.d.ts +45 -0
  21. package/dist/sources/_shared/specials-utils.d.ts.map +1 -0
  22. package/dist/sources/_shared/specials-utils.js +124 -0
  23. package/dist/sources/rbx/deps.d.ts +18 -0
  24. package/dist/sources/rbx/deps.d.ts.map +1 -0
  25. package/dist/sources/rbx/deps.js +192 -0
  26. package/dist/sources/rbx/index.d.ts +3 -0
  27. package/dist/sources/rbx/index.d.ts.map +1 -0
  28. package/dist/sources/rbx/index.js +10 -0
  29. package/dist/sources/rbx/mapping.d.ts +49 -0
  30. package/dist/sources/rbx/mapping.d.ts.map +1 -0
  31. package/dist/sources/rbx/mapping.js +924 -0
  32. package/dist/sources/rbx/responsive.d.ts +30 -0
  33. package/dist/sources/rbx/responsive.d.ts.map +1 -0
  34. package/dist/sources/rbx/responsive.js +279 -0
  35. package/dist/sources/rbx/specials.d.ts +21 -0
  36. package/dist/sources/rbx/specials.d.ts.map +1 -0
  37. package/dist/sources/rbx/specials.js +706 -0
  38. package/dist/sources/rbx/styles.d.ts +21 -0
  39. package/dist/sources/rbx/styles.d.ts.map +1 -0
  40. package/dist/sources/rbx/styles.js +29 -0
  41. package/dist/sources/rbx/transform.d.ts +25 -0
  42. package/dist/sources/rbx/transform.d.ts.map +1 -0
  43. package/dist/sources/rbx/transform.js +911 -0
  44. package/dist/sources/react-bulma-components/deps.d.ts.map +1 -1
  45. package/dist/sources/react-bulma-components/deps.js +17 -3
  46. package/dist/sources/react-bulma-components/responsive.d.ts +1 -1
  47. package/dist/sources/react-bulma-components/responsive.d.ts.map +1 -1
  48. package/dist/sources/react-bulma-components/responsive.js +1 -1
  49. package/dist/sources/react-bulma-components/specials.d.ts +3 -9
  50. package/dist/sources/react-bulma-components/specials.d.ts.map +1 -1
  51. package/dist/sources/react-bulma-components/specials.js +4 -91
  52. package/dist/sources/react-bulma-components/styles.d.ts +9 -9
  53. package/dist/sources/react-bulma-components/styles.d.ts.map +1 -1
  54. package/dist/sources/react-bulma-components/styles.js +17 -389
  55. package/dist/sources/react-bulma-components/transform.d.ts.map +1 -1
  56. package/dist/sources/react-bulma-components/transform.js +378 -101
  57. package/dist/sources/registry.d.ts.map +1 -1
  58. package/dist/sources/registry.js +2 -0
  59. package/dist/types.d.ts +8 -0
  60. package/dist/types.d.ts.map +1 -1
  61. package/package.json +8 -7
  62. package/dist/sources/react-bulma-components/jsx-utils.d.ts.map +0 -1
  63. package/dist/sources/react-bulma-components/props.d.ts +0 -15
  64. package/dist/sources/react-bulma-components/props.d.ts.map +0 -1
@@ -0,0 +1,924 @@
1
+ /**
2
+ * rbx (v2) → @allxsmith/bestax-bulma mapping tables.
3
+ *
4
+ * Data only — no AST work. `MAPPING` is the single source of truth for what
5
+ * the transform does with each rbx export; `RBX_EXPORTS` vendors rbx's public
6
+ * surface so `mapping-coverage.test.ts` can close it in both directions.
7
+ *
8
+ * rbx and bestax both target Bulma, and rbx's helper vocabulary lines up with
9
+ * `useBulmaClasses` far more closely than react-bulma-components' did — most
10
+ * of the value unions below are identical on both sides, so they pass through
11
+ * rather than being value-mapped. Where they diverge it is because Bulma v1
12
+ * itself changed (`is-marginless` became `m-0`), and those are recorded as
13
+ * explicit conversions rather than guesses.
14
+ */
15
+ const DOCS = 'https://bestax.io/docs';
16
+ /**
17
+ * rbx's `shades` union that bestax's `validColors` does not carry. Every other
18
+ * rbx colour and shade name exists in bestax verbatim.
19
+ */
20
+ const SHADE_TODO = {
21
+ 'white-ter': `\`white-ter\` is not a bestax colour; use \`white\` or a custom class (${DOCS}/api/helpers/usebulmaclasses)`,
22
+ 'white-bis': `\`white-bis\` is not a bestax colour; use \`white\` or a custom class (${DOCS}/api/helpers/usebulmaclasses)`,
23
+ };
24
+ /** rbx sizes are numbers; every bestax size prop takes the string form. */
25
+ const numeric = { numberToString: true };
26
+ /**
27
+ * `as` is universal in rbx — `forwardRefAs` puts it on every component — but
28
+ * bestax declares it on only ~16 components, several of them constrained to a
29
+ * narrow literal union (`Footer` is `'footer' | 'div'`). So the default is a
30
+ * TODO, and the components that really do accept `as` opt in with `AS_OK` in
31
+ * their own prop map, which runs first and wins.
32
+ */
33
+ const FILE_PART_TODO = `bestax's \`<File>\` renders the whole file structure from its own props — drop this element and set \`label\`, \`hasName\`, \`isBoxed\` etc. on \`<File>\` (${DOCS}/api/form/file)`;
34
+ const AS_TODO = {
35
+ todo: `bestax declares \`as\` on only some components, and this is not one of them; restructure the element or render the tag directly (${DOCS}/api)`,
36
+ };
37
+ /**
38
+ * Components whose bestax counterpart really does accept `as`. An empty
39
+ * action passes the prop through untouched; its job is to claim the name so
40
+ * the universal `AS_TODO` never sees it.
41
+ *
42
+ * Several of these narrow `as` to a literal union (`Footer` is
43
+ * `'footer' | 'div'`, `Control` is `'div'`), so an `as={SomeComponent}` still
44
+ * surfaces — as a type error the user can see, rather than a silent rewrite.
45
+ */
46
+ const AS_OK = {};
47
+ /**
48
+ * Helper props rbx mixes into every component via `HelpersProps`. Applied
49
+ * after each component's own prop map, to whatever attributes are left.
50
+ *
51
+ * `badge*`, `tooltip*` and `responsive` are deliberately absent: they are
52
+ * consumed structurally (see specials.ts / responsive.ts) before this pass
53
+ * runs, and letting them fall through here would be wrong — bestax has its
54
+ * own unrelated `responsive` prop (`'mobile' | 'narrow'`), so a pass-through
55
+ * would silently produce a type error rather than a TODO.
56
+ */
57
+ export const UNIVERSAL_PROPS = {
58
+ // --- typography: values are identical on both sides ---------------------
59
+ backgroundColor: { rename: 'bgColor', valueTodo: SHADE_TODO },
60
+ textColor: { valueTodo: SHADE_TODO },
61
+ textAlign: {}, // centered | justified | left | right — same union
62
+ textTransform: {}, // capitalized | lowercase | uppercase — bestax adds italic
63
+ textWeight: {}, // light | medium | normal | semibold | bold — same union
64
+ textSize: numeric, // rbx 1–7 as numbers, bestax as strings
65
+ italic: { booleanToProp: { name: 'textTransform', value: 'italic' } },
66
+ // --- float / overflow / overlay -----------------------------------------
67
+ clearfix: {},
68
+ pull: { rename: 'float' }, // left | right — same union
69
+ clipped: { booleanToProp: { name: 'overflow', value: 'clipped' } },
70
+ overlay: {}, // boolean on both sides
71
+ // --- visibility ----------------------------------------------------------
72
+ hidden: { booleanToProp: { name: 'visibility', value: 'hidden' } },
73
+ invisible: { booleanToProp: { name: 'visibility', value: 'invisible' } },
74
+ srOnly: { booleanToProp: { name: 'visibility', value: 'sr-only' } },
75
+ // --- Bulma v1 dropped the `is-*less` spacing helpers ---------------------
76
+ marginless: { booleanToProp: { name: 'm', value: '0' } },
77
+ paddingless: { booleanToProp: { name: 'p', value: '0' } },
78
+ radiusless: { booleanToProp: { name: 'radius', value: 'radiusless' } },
79
+ shadowless: { booleanToProp: { name: 'shadow', value: 'shadowless' } },
80
+ unselectable: {
81
+ booleanToProp: { name: 'interaction', value: 'unselectable' },
82
+ },
83
+ relative: {}, // boolean on both sides
84
+ // Per-component maps override this with AS_OK where bestax has an `as`.
85
+ as: AS_TODO,
86
+ };
87
+ /** rbx badge helper props → bestax `<Badge>` props. */
88
+ export const BADGE_PROPS = {
89
+ badge: 'content',
90
+ badgeColor: 'color',
91
+ // bestax's Badge has no outline, pill or size variants.
92
+ badgeOutlined: null,
93
+ badgeRounded: null,
94
+ badgeSize: null,
95
+ };
96
+ /** rbx tooltip helper props → bestax `<Tooltip>` props. */
97
+ export const TOOLTIP_PROPS = {
98
+ tooltip: 'label',
99
+ tooltipActive: 'active',
100
+ tooltipColor: 'color',
101
+ tooltipMultiline: 'multiline',
102
+ tooltipPosition: 'position',
103
+ // A breakpoint→position object; bestax has one position for all viewports.
104
+ tooltipResponsive: null,
105
+ };
106
+ /**
107
+ * rbx breakpoints → the suffix bestax uses on its viewport-aware helper props
108
+ * (`displayTablet`, `textSizeDesktop`, …). `null` means bestax has no
109
+ * equivalent viewport, so the value becomes a TODO rather than a guess.
110
+ */
111
+ export const RESPONSIVE_BREAKPOINTS = {
112
+ mobile: 'Mobile',
113
+ tablet: 'Tablet',
114
+ desktop: 'Desktop',
115
+ widescreen: 'Widescreen',
116
+ fullhd: 'Fullhd',
117
+ // Bulma v1 keeps `is-hidden-touch`, but bestax's validViewports does not
118
+ // include it, so there is no prop to write.
119
+ touch: null,
120
+ };
121
+ export const MAPPING = {
122
+ // ---- base ---------------------------------------------------------------
123
+ Generic: {
124
+ status: 'todo',
125
+ todo: `\`Generic\` is rbx's untyped base element; render the underlying HTML tag directly, or use a bestax component (${DOCS}/api)`,
126
+ },
127
+ forwardRefAs: {
128
+ status: 'todo',
129
+ todo: '`forwardRefAs` is rbx-internal and has no counterpart. Only the bestax form controls and a few components (Dialog, Sidebar, Toast, Carousel) forward refs; most, Button and Box included, are plain function components that do not — put the ref on a wrapping element you control',
130
+ },
131
+ // ---- elements -----------------------------------------------------------
132
+ Block: { status: 'mapped', target: 'Block' },
133
+ Box: { status: 'mapped', target: 'Box' },
134
+ Button: {
135
+ status: 'mapped',
136
+ target: 'Button',
137
+ props: {
138
+ as: AS_OK,
139
+ color: {},
140
+ size: {},
141
+ outlined: { booleanToProp: { name: 'isOutlined' } },
142
+ rounded: { booleanToProp: { name: 'isRounded' } },
143
+ inverted: { booleanToProp: { name: 'isInverted' } },
144
+ fullwidth: { booleanToProp: { name: 'isFullwidth' } },
145
+ static: { booleanToProp: { name: 'isStatic' } },
146
+ state: {
147
+ valueMap: {
148
+ hovered: 'isHovered',
149
+ focused: 'isFocused',
150
+ active: 'isActive',
151
+ loading: 'isLoading',
152
+ },
153
+ valueToProp: true,
154
+ },
155
+ selected: {
156
+ todo: 'no bestax equivalent (Bulma is-selected in button groups)',
157
+ },
158
+ text: {
159
+ todo: `no bestax equivalent for the \`is-text\` button; use \`<Button color="ghost">\` or a link (${DOCS}/api/elements/button)`,
160
+ },
161
+ },
162
+ subs: {
163
+ Group: {
164
+ status: 'mapped',
165
+ target: 'Buttons',
166
+ props: {
167
+ hasAddons: {},
168
+ align: {
169
+ valueMap: { centered: 'isCentered', right: 'isRight' },
170
+ valueToProp: true,
171
+ },
172
+ size: {
173
+ todo: 'bestax `Buttons` has no size prop; set `size` on each Button instead',
174
+ },
175
+ },
176
+ },
177
+ },
178
+ },
179
+ Container: {
180
+ status: 'mapped',
181
+ target: 'Container',
182
+ props: {
183
+ fluid: {},
184
+ // bestax's ContainerBreakpoint is 'tablet' | 'desktop' | 'widescreen';
185
+ // rbx accepts all six, so the other three are type errors if passed on.
186
+ breakpoint: {
187
+ valueTodo: {
188
+ mobile: "bestax's Container has no mobile breakpoint (Bulma containers start at tablet); drop it",
189
+ fullhd: "bestax's Container breakpoint stops at widescreen; use `isMax` or a custom class for fullhd",
190
+ touch: "bestax's Container has no touch breakpoint; drop it or use a custom class",
191
+ },
192
+ },
193
+ },
194
+ },
195
+ Content: {
196
+ status: 'mapped',
197
+ target: 'Content',
198
+ props: { size: {} },
199
+ subs: {
200
+ OrderedList: {
201
+ status: 'mapped',
202
+ target: 'OrderedList',
203
+ props: {
204
+ type: {
205
+ todo: 'bestax `OrderedList` has no `type` prop; set the `type` attribute on the underlying <ol> or a custom class',
206
+ },
207
+ },
208
+ subs: {
209
+ Item: { status: 'mapped', target: 'OrderedList.Item' },
210
+ },
211
+ },
212
+ },
213
+ },
214
+ Delete: { status: 'mapped', target: 'Delete', props: { size: {} } },
215
+ Divider: {
216
+ status: 'partial',
217
+ target: 'Divider',
218
+ special: 'divider',
219
+ props: {
220
+ color: {
221
+ todo: 'bestax `Divider` takes only `bgColor`; set that or a custom class',
222
+ },
223
+ vertical: {
224
+ todo: 'bestax `Divider` has no vertical variant; use a bordered Column or custom CSS',
225
+ },
226
+ },
227
+ },
228
+ Heading: { status: 'mapped', special: 'heading' },
229
+ Highlight: {
230
+ status: 'todo',
231
+ todo: `\`Highlight\` (syntax-highlighted <pre>) has no bestax equivalent; use \`<Pre>\` plus your own highlighter (${DOCS}/api/elements/pre)`,
232
+ },
233
+ Icon: { status: 'partial', target: 'Icon', special: 'icon' },
234
+ Image: {
235
+ status: 'mapped',
236
+ target: 'Image',
237
+ props: { as: AS_OK, rounded: { booleanToProp: { name: 'isRounded' } } },
238
+ subs: {
239
+ // rbx nests <Image> inside <Image.Container>; bestax's Image renders the
240
+ // figure itself, so the container collapses into it.
241
+ Container: { status: 'mapped', special: 'image-container' },
242
+ },
243
+ },
244
+ Notification: {
245
+ status: 'mapped',
246
+ target: 'Notification',
247
+ props: { color: {} },
248
+ },
249
+ Numeric: {
250
+ status: 'todo',
251
+ todo: '`Numeric` (locale number formatting) has no bestax equivalent; use Intl.NumberFormat directly',
252
+ },
253
+ // NOT `Loading`: rbx's Loader always renders `div.loader`, while bestax's
254
+ // Loading is an overlay that defaults `active` to false and returns null —
255
+ // so a direct map made every migrated loader disappear. Bulma's `.loader`
256
+ // is a plain element, and the react-bulma-components source already
257
+ // emits exactly that for its own `Loader`.
258
+ Loader: { status: 'mapped', special: 'loader' },
259
+ PageLoader: {
260
+ status: 'mapped',
261
+ target: 'Loading',
262
+ special: 'page-loader',
263
+ props: {
264
+ active: {},
265
+ color: {},
266
+ direction: {
267
+ todo: 'bestax `Loading` has no directional variant; drop it or add a custom class',
268
+ },
269
+ },
270
+ },
271
+ Progress: {
272
+ status: 'mapped',
273
+ target: 'Progress',
274
+ props: { color: {}, size: {}, max: {}, value: {} },
275
+ },
276
+ Table: {
277
+ status: 'mapped',
278
+ target: 'Table',
279
+ props: {
280
+ bordered: { booleanToProp: { name: 'isBordered' } },
281
+ striped: { booleanToProp: { name: 'isStriped' } },
282
+ narrow: { booleanToProp: { name: 'isNarrow' } },
283
+ hoverable: { booleanToProp: { name: 'isHoverable' } },
284
+ fullwidth: { booleanToProp: { name: 'isFullwidth' } },
285
+ },
286
+ subs: {
287
+ Body: { status: 'mapped', target: 'Table.Tbody' },
288
+ Cell: { status: 'mapped', target: 'Table.Td' },
289
+ Foot: { status: 'mapped', target: 'Table.Tfoot' },
290
+ Head: { status: 'mapped', target: 'Table.Thead' },
291
+ Heading: { status: 'mapped', target: 'Table.Th' },
292
+ Row: {
293
+ status: 'mapped',
294
+ target: 'Table.Tr',
295
+ props: {
296
+ selected: {
297
+ todo: 'bestax `Table.Tr` has no `selected` prop; add className="is-selected"',
298
+ },
299
+ },
300
+ },
301
+ },
302
+ },
303
+ Tag: {
304
+ status: 'mapped',
305
+ target: 'Tag',
306
+ props: {
307
+ color: {},
308
+ size: {},
309
+ rounded: { booleanToProp: { name: 'isRounded' } },
310
+ delete: { booleanToProp: { name: 'isDelete' } },
311
+ },
312
+ subs: {
313
+ Group: {
314
+ status: 'mapped',
315
+ target: 'Tags',
316
+ props: {
317
+ gapless: { booleanToProp: { name: 'hasAddons' } },
318
+ size: {
319
+ todo: 'bestax `Tags` has no size prop; set `size` on each Tag instead',
320
+ },
321
+ },
322
+ },
323
+ },
324
+ },
325
+ Title: { status: 'mapped', special: 'title', props: { as: AS_OK } },
326
+ Tile: {
327
+ status: 'todo',
328
+ todo: `Bulma v1 replaced tiles with the Grid/Cell components — see ${DOCS}/api/grid and the migration guide ${DOCS}/guides/getting-started/migration/bulma-0-9-to-1`,
329
+ },
330
+ // ---- form ---------------------------------------------------------------
331
+ Checkbox: { status: 'mapped', target: 'Checkbox' },
332
+ Control: {
333
+ status: 'mapped',
334
+ target: 'Control',
335
+ props: {
336
+ as: AS_OK,
337
+ size: {},
338
+ expanded: { booleanToProp: { name: 'isExpanded' } },
339
+ loading: { booleanToProp: { name: 'isLoading' } },
340
+ // rbx's iconLeft/iconRight are booleans; bestax's same-named props take
341
+ // an IconProps object, so they map onto the has* booleans instead.
342
+ iconLeft: { booleanToProp: { name: 'hasIconsLeft' } },
343
+ iconRight: { booleanToProp: { name: 'hasIconsRight' } },
344
+ },
345
+ },
346
+ Field: {
347
+ status: 'mapped',
348
+ target: 'Field',
349
+ special: 'field',
350
+ props: {
351
+ horizontal: {},
352
+ // rbx accepts `expanded` on the Field, but Bulma's `is-expanded` is a
353
+ // Control modifier and bestax follows Bulma: `FieldProps` has no such
354
+ // prop, so passing it through was a silent type error.
355
+ expanded: {
356
+ todo: 'bestax follows Bulma in putting `is-expanded` on the Control, not the Field; move this to the `<Control isExpanded>` inside it',
357
+ },
358
+ narrow: {},
359
+ align: {
360
+ todo: 'rbx\'s `align` on a Field is Bulma\'s grouped alignment; bestax\'s `grouped` takes it as its value — use `grouped="centered"` or `grouped="right"`',
361
+ },
362
+ kind: {
363
+ valueMap: { addons: 'hasAddons', group: 'grouped' },
364
+ valueToProp: true,
365
+ },
366
+ multiline: {},
367
+ },
368
+ subs: {
369
+ Label: { status: 'mapped', target: 'Field.Label', props: { size: {} } },
370
+ Body: { status: 'mapped', target: 'Field.Body' },
371
+ },
372
+ },
373
+ Fieldset: {
374
+ status: 'todo',
375
+ todo: 'bestax has no `Fieldset` component; render a plain <fieldset> (its `disabled` attribute works natively)',
376
+ },
377
+ File: {
378
+ status: 'mapped',
379
+ target: 'File',
380
+ props: {
381
+ color: {},
382
+ size: {},
383
+ boxed: { booleanToProp: { name: 'isBoxed' } },
384
+ fullwidth: { booleanToProp: { name: 'isFullwidth' } },
385
+ hasName: {},
386
+ align: {
387
+ valueMap: { centered: 'isCentered', right: 'isRight' },
388
+ valueToProp: true,
389
+ },
390
+ },
391
+ // bestax's `<File>` renders the whole Bulma file structure itself from
392
+ // props (`label`, `hasName`, `isBoxed`, …), so rbx's structural pieces
393
+ // have no counterpart to become. Collapsing them automatically would mean
394
+ // guessing which prop each one was standing in for.
395
+ subs: {
396
+ CTA: { status: 'todo', todo: FILE_PART_TODO },
397
+ Icon: { status: 'todo', todo: FILE_PART_TODO },
398
+ Input: { status: 'todo', todo: FILE_PART_TODO },
399
+ Label: { status: 'todo', todo: FILE_PART_TODO },
400
+ Name: { status: 'todo', todo: FILE_PART_TODO },
401
+ },
402
+ },
403
+ Help: { status: 'mapped', special: 'help' },
404
+ Input: {
405
+ status: 'mapped',
406
+ target: 'Input',
407
+ props: {
408
+ color: {},
409
+ size: {},
410
+ type: {},
411
+ readOnly: {},
412
+ rounded: { booleanToProp: { name: 'isRounded' } },
413
+ static: { booleanToProp: { name: 'isStatic' } },
414
+ state: {
415
+ valueMap: { focused: 'isFocused', hovered: 'isHovered' },
416
+ valueToProp: true,
417
+ },
418
+ },
419
+ },
420
+ Label: { status: 'mapped', special: 'label' },
421
+ Radio: { status: 'mapped', target: 'Radio' },
422
+ Select: {
423
+ status: 'mapped',
424
+ target: 'Select',
425
+ props: { size: {} },
426
+ subs: {
427
+ // rbx wraps <Select> in <Select.Container>; bestax's Select renders the
428
+ // wrapper itself.
429
+ Container: { status: 'mapped', special: 'select-container' },
430
+ Option: { status: 'mapped', special: 'plain-option' },
431
+ },
432
+ },
433
+ Textarea: {
434
+ status: 'mapped',
435
+ target: 'TextArea',
436
+ props: {
437
+ color: {},
438
+ size: {},
439
+ fixedSize: { booleanToProp: { name: 'hasFixedSize' } },
440
+ state: {
441
+ valueMap: { focused: 'isFocused', hovered: 'isHovered' },
442
+ valueToProp: true,
443
+ },
444
+ },
445
+ },
446
+ // ---- components ---------------------------------------------------------
447
+ Breadcrumb: {
448
+ status: 'mapped',
449
+ target: 'Breadcrumb',
450
+ props: {
451
+ // rbx's alignments and separators are already bestax's unions.
452
+ align: { rename: 'alignment' },
453
+ separator: {},
454
+ size: {},
455
+ },
456
+ subs: { Item: { status: 'mapped', special: 'breadcrumb-item' } },
457
+ },
458
+ Card: {
459
+ status: 'mapped',
460
+ target: 'Card',
461
+ subs: {
462
+ Content: { status: 'mapped', target: 'Card.Content' },
463
+ Image: { status: 'mapped', target: 'Card.Image' },
464
+ Header: {
465
+ status: 'mapped',
466
+ target: 'Card.Header',
467
+ subs: {
468
+ Title: {
469
+ status: 'mapped',
470
+ target: 'Card.Header.Title',
471
+ props: {
472
+ align: {
473
+ valueMap: { centered: 'centered' },
474
+ valueToProp: true,
475
+ },
476
+ },
477
+ },
478
+ Icon: { status: 'mapped', target: 'Card.Header.Icon' },
479
+ },
480
+ },
481
+ Footer: {
482
+ status: 'mapped',
483
+ target: 'Card.Footer',
484
+ subs: { Item: { status: 'mapped', target: 'Card.FooterItem' } },
485
+ },
486
+ },
487
+ },
488
+ Dropdown: {
489
+ status: 'partial',
490
+ target: 'Dropdown',
491
+ special: 'dropdown',
492
+ props: {
493
+ active: {},
494
+ hoverable: {},
495
+ up: {},
496
+ align: { valueMap: { right: 'right' }, valueToProp: true },
497
+ managed: {
498
+ todo: 'bestax `Dropdown` is uncontrolled by default; use `active` + `onActiveChange` for controlled behaviour',
499
+ },
500
+ innerRef: {
501
+ todo: 'this bestax component is a plain function component and forwards no ref; drop `innerRef`, or put the ref on a wrapping element you control',
502
+ },
503
+ },
504
+ subs: {
505
+ Container: { status: 'mapped', special: 'dropdown-container' },
506
+ Content: { status: 'mapped', special: 'dropdown-content' },
507
+ Menu: { status: 'mapped', special: 'dropdown-menu' },
508
+ Trigger: { status: 'mapped', special: 'dropdown-trigger' },
509
+ Item: {
510
+ status: 'mapped',
511
+ target: 'Dropdown.Item',
512
+ // `as` lives on DropdownItemProps ('a' | 'div' | 'button'), not on
513
+ // the Dropdown root.
514
+ props: { as: AS_OK, active: {}, onClick: {} },
515
+ },
516
+ Divider: { status: 'mapped', target: 'Dropdown.Divider' },
517
+ Context: {
518
+ status: 'todo',
519
+ todo: '`Dropdown.Context` is rbx-internal; bestax exposes `active`/`onActiveChange` on `<Dropdown>` instead',
520
+ },
521
+ },
522
+ },
523
+ Level: {
524
+ status: 'mapped',
525
+ target: 'Level',
526
+ props: {
527
+ breakpoint: {
528
+ valueMap: { mobile: 'isMobile' },
529
+ valueToProp: true,
530
+ valueTodo: {
531
+ tablet: 'bestax `Level` only supports the mobile breakpoint',
532
+ desktop: 'bestax `Level` only supports the mobile breakpoint',
533
+ widescreen: 'bestax `Level` only supports the mobile breakpoint',
534
+ fullhd: 'bestax `Level` only supports the mobile breakpoint',
535
+ touch: 'bestax `Level` only supports the mobile breakpoint',
536
+ },
537
+ },
538
+ },
539
+ subs: {
540
+ Item: { status: 'mapped', special: 'level-item', props: { as: AS_OK } },
541
+ },
542
+ },
543
+ List: {
544
+ status: 'todo',
545
+ todo: `rbx's \`List\` is the bulma-list extension, which Bulma v1 does not ship; use \`<UnorderedList>\` or \`<Menu>\` (${DOCS}/api/elements/unorderedlist)`,
546
+ subs: {
547
+ Item: {
548
+ status: 'todo',
549
+ todo: `rbx's \`List.Item\` is the bulma-list extension, which Bulma v1 does not ship; use \`<UnorderedList.Item>\` or \`<Menu.Item>\` (${DOCS}/api/elements/unorderedlist)`,
550
+ },
551
+ },
552
+ },
553
+ Media: {
554
+ status: 'mapped',
555
+ target: 'Media',
556
+ props: { as: AS_OK },
557
+ subs: {
558
+ // `as` is NOT opted in here: Media.Item resolves to Media.Left,
559
+ // Media.Content or Media.Right by its `align` value, and only
560
+ // MediaLeftProps declares `as`. The special decides per target.
561
+ Item: { status: 'mapped', special: 'media-item' },
562
+ },
563
+ },
564
+ Menu: {
565
+ status: 'mapped',
566
+ target: 'Menu',
567
+ subs: {
568
+ Label: { status: 'mapped', target: 'Menu.Label' },
569
+ List: {
570
+ status: 'mapped',
571
+ target: 'Menu.List',
572
+ subs: {
573
+ Item: {
574
+ status: 'mapped',
575
+ target: 'Menu.Item',
576
+ props: {
577
+ as: AS_OK,
578
+ active: {},
579
+ menu: {
580
+ todo: 'bestax `Menu.Item` has no `menu` prop; nest a `<Menu.List>` as a child instead',
581
+ },
582
+ },
583
+ },
584
+ },
585
+ },
586
+ },
587
+ },
588
+ Message: {
589
+ status: 'mapped',
590
+ target: 'Message',
591
+ props: {
592
+ color: {},
593
+ size: {
594
+ todo: 'bestax `Message` has no size prop; use the `size` helper on its children',
595
+ },
596
+ },
597
+ subs: {
598
+ Body: { status: 'mapped', target: 'Message.Body' },
599
+ Header: { status: 'mapped', target: 'Message.Header' },
600
+ },
601
+ },
602
+ Modal: {
603
+ status: 'partial',
604
+ target: 'Modal',
605
+ special: 'modal',
606
+ props: {
607
+ active: {},
608
+ onClose: {},
609
+ closeOnEsc: {
610
+ todo: 'bestax `Modal` implements no Escape handling at all; add your own keydown listener, or drop the prop if the behaviour is not needed',
611
+ },
612
+ closeOnBlur: {
613
+ todo: "bestax `Modal` has no built-in background-click close; wire it through `Modal.Background`'s onClick and `onClose`",
614
+ },
615
+ document: {
616
+ todo: 'bestax `Modal` renders inline where you place it rather than portalling into a document at all; drop this prop, and see the advisory the codemod adds on every Modal',
617
+ },
618
+ containerClassName: { rename: 'className' },
619
+ innerRef: {
620
+ todo: 'this bestax component is a plain function component and forwards no ref; drop `innerRef`, or put the ref on a wrapping element you control',
621
+ },
622
+ },
623
+ subs: {
624
+ Background: { status: 'mapped', target: 'Modal.Background' },
625
+ Close: { status: 'mapped', target: 'Modal.Close' },
626
+ Content: { status: 'mapped', target: 'Modal.Content' },
627
+ Container: { status: 'mapped', special: 'modal-container' },
628
+ Card: {
629
+ status: 'mapped',
630
+ target: 'Modal.Card',
631
+ subs: {
632
+ Body: { status: 'mapped', target: 'Modal.Card.Body' },
633
+ Foot: { status: 'mapped', target: 'Modal.Card.Foot' },
634
+ Head: { status: 'mapped', target: 'Modal.Card.Head' },
635
+ Title: { status: 'mapped', target: 'Modal.Card.Title' },
636
+ },
637
+ },
638
+ Context: {
639
+ status: 'todo',
640
+ todo: '`Modal.Context` is rbx-internal; bestax exposes `active`/`onClose` on `<Modal>` instead',
641
+ },
642
+ Portal: {
643
+ status: 'todo',
644
+ todo: '`Modal.Portal` is rbx-internal; bestax `<Modal>` renders inline and does not portal at all — wrap it in `createPortal` yourself if you relied on that (see the advisory the codemod adds on every Modal)',
645
+ },
646
+ },
647
+ },
648
+ Navbar: {
649
+ status: 'mapped',
650
+ target: 'Navbar',
651
+ props: {
652
+ color: {},
653
+ fixed: {},
654
+ transparent: {},
655
+ active: {
656
+ todo: 'bestax `Navbar` manages burger state itself; drop `active` or control the burger directly',
657
+ },
658
+ managed: {
659
+ todo: 'bestax `Navbar` is uncontrolled; drop `managed`',
660
+ },
661
+ document: { todo: 'bestax `Navbar` has no `document` prop; drop it' },
662
+ innerRef: {
663
+ todo: 'this bestax component is a plain function component and forwards no ref; drop `innerRef`, or put the ref on a wrapping element you control',
664
+ },
665
+ },
666
+ subs: {
667
+ Brand: { status: 'mapped', target: 'Navbar.Brand' },
668
+ Burger: { status: 'mapped', target: 'Navbar.Burger' },
669
+ Divider: { status: 'mapped', target: 'Navbar.Divider' },
670
+ Menu: { status: 'mapped', target: 'Navbar.Menu' },
671
+ Link: {
672
+ status: 'mapped',
673
+ target: 'Navbar.Link',
674
+ props: {
675
+ as: AS_OK,
676
+ arrowless: {},
677
+ onClick: {},
678
+ },
679
+ },
680
+ Item: {
681
+ status: 'mapped',
682
+ special: 'navbar-item',
683
+ props: { active: {}, onClick: {}, as: AS_OK },
684
+ subs: { Container: { status: 'mapped', special: 'navbar-container' } },
685
+ },
686
+ Dropdown: { status: 'mapped', special: 'navbar-dropdown' },
687
+ Segment: { status: 'mapped', special: 'navbar-segment' },
688
+ Container: { status: 'mapped', special: 'navbar-container' },
689
+ Context: {
690
+ status: 'todo',
691
+ todo: '`Navbar.Context` is rbx-internal; bestax `<Navbar>` manages its own state',
692
+ },
693
+ },
694
+ },
695
+ Pagination: {
696
+ status: 'mapped',
697
+ target: 'Pagination',
698
+ props: {
699
+ align: {},
700
+ size: {},
701
+ rounded: {},
702
+ },
703
+ subs: {
704
+ Link: { status: 'mapped', target: 'Pagination.Link' },
705
+ List: { status: 'mapped', target: 'Pagination.List' },
706
+ Ellipsis: { status: 'mapped', target: 'Pagination.Ellipsis' },
707
+ Step: { status: 'mapped', special: 'pagination-step' },
708
+ },
709
+ },
710
+ Panel: {
711
+ status: 'mapped',
712
+ target: 'Panel',
713
+ props: { color: {} },
714
+ subs: {
715
+ Heading: { status: 'mapped', target: 'Panel.Heading' },
716
+ Block: { status: 'mapped', target: 'Panel.Block', props: { active: {} } },
717
+ Icon: { status: 'partial', target: 'Panel.Icon', special: 'icon' },
718
+ Tab: {
719
+ status: 'mapped',
720
+ special: 'panel-tab',
721
+ subs: { Group: { status: 'mapped', target: 'Panel.Tabs' } },
722
+ },
723
+ },
724
+ },
725
+ Tab: {
726
+ // bestax's `Tabs.Tab` is the controlled API and requires an `index` that
727
+ // cannot be derived from rbx's markup; `Tabs.Item` is the plain <li> that
728
+ // rbx's `Tab` actually is.
729
+ status: 'mapped',
730
+ target: 'Tabs.Item',
731
+ props: { active: {} },
732
+ subs: {
733
+ Group: {
734
+ status: 'mapped',
735
+ special: 'tab-group',
736
+ target: 'Tabs',
737
+ props: {
738
+ align: {},
739
+ size: {},
740
+ fullwidth: { booleanToProp: { name: 'isFullwidth' } },
741
+ kind: {
742
+ valueMap: {
743
+ boxed: 'boxed',
744
+ toggle: 'toggle',
745
+ 'toggle-rounded': 'toggle rounded',
746
+ },
747
+ valueToProp: true,
748
+ },
749
+ },
750
+ },
751
+ },
752
+ },
753
+ // ---- grid ---------------------------------------------------------------
754
+ Column: {
755
+ status: 'mapped',
756
+ target: 'Column',
757
+ special: 'column',
758
+ props: {
759
+ // bestax's BulmaColumnSize accepts numbers as well as the named
760
+ // strings, so rbx's numeric sizes carry over untouched.
761
+ size: {},
762
+ offset: {},
763
+ narrow: { booleanToProp: { name: 'isNarrow' } },
764
+ },
765
+ subs: {
766
+ Group: {
767
+ status: 'mapped',
768
+ target: 'Columns',
769
+ special: 'column-group',
770
+ props: {
771
+ centered: { booleanToProp: { name: 'isCentered' } },
772
+ gapless: { booleanToProp: { name: 'isGapless' } },
773
+ multiline: { booleanToProp: { name: 'isMultiline' } },
774
+ vcentered: { booleanToProp: { name: 'isVCentered' } },
775
+ // bestax's `gapSize*` props are deprecated aliases of `gap*`, and
776
+ // both take the same numeric 0-8 scale — so this is a rename, not
777
+ // a value conversion.
778
+ gapSize: { rename: 'gap' },
779
+ breakpoint: {
780
+ valueMap: { mobile: 'isMobile', desktop: 'isDesktop' },
781
+ valueToProp: true,
782
+ valueTodo: {
783
+ tablet: 'bestax `Columns` supports only the mobile and desktop breakpoints',
784
+ widescreen: 'bestax `Columns` supports only the mobile and desktop breakpoints',
785
+ fullhd: 'bestax `Columns` supports only the mobile and desktop breakpoints',
786
+ touch: 'bestax `Columns` supports only the mobile and desktop breakpoints',
787
+ },
788
+ },
789
+ },
790
+ },
791
+ },
792
+ },
793
+ // ---- layout -------------------------------------------------------------
794
+ Footer: { status: 'mapped', target: 'Footer', props: { as: AS_OK } },
795
+ Hero: {
796
+ status: 'mapped',
797
+ target: 'Hero',
798
+ props: {
799
+ color: {},
800
+ size: {
801
+ valueTodo: {
802
+ 'fullheight-with-navbar': 'set `size="fullheight"` plus `fullheightWithNavbar` on the bestax Hero',
803
+ },
804
+ },
805
+ gradient: {
806
+ todo: 'bestax `Hero` has no gradient variant; add a custom class or `bgColor`',
807
+ },
808
+ },
809
+ subs: {
810
+ Body: { status: 'mapped', target: 'Hero.Body' },
811
+ Foot: { status: 'mapped', target: 'Hero.Foot' },
812
+ Head: { status: 'mapped', target: 'Hero.Head' },
813
+ },
814
+ },
815
+ Section: { status: 'mapped', target: 'Section', props: { size: {} } },
816
+ };
817
+ /**
818
+ * rbx's public export surface, vendored from its five `index.ts` barrels at
819
+ * the pinned SHA. Values are the dot-notation sub-paths each export carries.
820
+ *
821
+ * `mapping-coverage.test.ts` walks this against `MAPPING` in both directions,
822
+ * so rbx coverage cannot silently regress and `MAPPING` cannot grow an entry
823
+ * for something rbx never exported.
824
+ */
825
+ export const RBX_EXPORTS = {
826
+ Block: [],
827
+ Box: [],
828
+ Breadcrumb: ['Item'],
829
+ Button: ['Group'],
830
+ Card: [
831
+ 'Content',
832
+ 'Footer',
833
+ 'Footer.Item',
834
+ 'Header',
835
+ 'Header.Icon',
836
+ 'Header.Title',
837
+ 'Image',
838
+ ],
839
+ Checkbox: [],
840
+ Column: ['Group'],
841
+ Container: [],
842
+ Content: ['OrderedList', 'OrderedList.Item'],
843
+ Control: [],
844
+ Delete: [],
845
+ Divider: [],
846
+ Dropdown: [
847
+ 'Container',
848
+ 'Content',
849
+ 'Context',
850
+ 'Divider',
851
+ 'Item',
852
+ 'Menu',
853
+ 'Trigger',
854
+ ],
855
+ Field: ['Body', 'Label'],
856
+ Fieldset: [],
857
+ File: ['CTA', 'Icon', 'Input', 'Label', 'Name'],
858
+ Footer: [],
859
+ Generic: [],
860
+ Heading: [],
861
+ Help: [],
862
+ Hero: ['Body', 'Foot', 'Head'],
863
+ Highlight: [],
864
+ Icon: [],
865
+ Image: ['Container'],
866
+ Input: [],
867
+ Label: [],
868
+ Level: ['Item'],
869
+ List: ['Item'],
870
+ Loader: [],
871
+ Media: ['Item'],
872
+ Menu: ['Label', 'List', 'List.Item'],
873
+ Message: ['Body', 'Header'],
874
+ Modal: [
875
+ 'Background',
876
+ 'Card',
877
+ 'Card.Body',
878
+ 'Card.Foot',
879
+ 'Card.Head',
880
+ 'Card.Title',
881
+ 'Close',
882
+ 'Container',
883
+ 'Content',
884
+ 'Context',
885
+ 'Portal',
886
+ ],
887
+ Navbar: [
888
+ 'Brand',
889
+ 'Burger',
890
+ 'Container',
891
+ 'Context',
892
+ 'Divider',
893
+ 'Dropdown',
894
+ 'Item',
895
+ 'Item.Container',
896
+ 'Link',
897
+ 'Menu',
898
+ 'Segment',
899
+ ],
900
+ Notification: [],
901
+ Numeric: [],
902
+ PageLoader: [],
903
+ Pagination: ['Ellipsis', 'Link', 'List', 'Step'],
904
+ Panel: ['Block', 'Heading', 'Icon', 'Tab', 'Tab.Group'],
905
+ Progress: [],
906
+ Radio: [],
907
+ Section: [],
908
+ Select: ['Container', 'Option'],
909
+ Tab: ['Group'],
910
+ Table: ['Body', 'Cell', 'Foot', 'Head', 'Heading', 'Row'],
911
+ Tag: ['Group'],
912
+ Textarea: [],
913
+ Tile: [],
914
+ Title: [],
915
+ forwardRefAs: [],
916
+ };
917
+ /** Walk a dotted component path through `MAPPING`. */
918
+ export function resolveMapping(path) {
919
+ let current = MAPPING[path[0]];
920
+ for (const segment of path.slice(1)) {
921
+ current = current?.subs?.[segment];
922
+ }
923
+ return current;
924
+ }