bestax-migrate 2.2.1 → 2.3.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.
Files changed (57) hide show
  1. package/README.md +12 -10
  2. package/dist/sources/_shared/imports.d.ts +28 -0
  3. package/dist/sources/_shared/imports.d.ts.map +1 -1
  4. package/dist/sources/_shared/imports.js +62 -0
  5. package/dist/sources/_shared/jsx-utils.d.ts +17 -0
  6. package/dist/sources/_shared/jsx-utils.d.ts.map +1 -1
  7. package/dist/sources/_shared/jsx-utils.js +49 -1
  8. package/dist/sources/_shared/props.d.ts +17 -0
  9. package/dist/sources/_shared/props.d.ts.map +1 -1
  10. package/dist/sources/_shared/props.js +72 -3
  11. package/dist/sources/_shared/specials-utils.d.ts +49 -3
  12. package/dist/sources/_shared/specials-utils.d.ts.map +1 -1
  13. package/dist/sources/_shared/specials-utils.js +165 -5
  14. package/dist/sources/_shared/viewports.d.ts +17 -0
  15. package/dist/sources/_shared/viewports.d.ts.map +1 -0
  16. package/dist/sources/_shared/viewports.js +32 -0
  17. package/dist/sources/bloomer/deps.d.ts +17 -0
  18. package/dist/sources/bloomer/deps.d.ts.map +1 -0
  19. package/dist/sources/bloomer/deps.js +119 -0
  20. package/dist/sources/bloomer/index.d.ts +3 -0
  21. package/dist/sources/bloomer/index.d.ts.map +1 -0
  22. package/dist/sources/bloomer/index.js +10 -0
  23. package/dist/sources/bloomer/mapping.d.ts +84 -0
  24. package/dist/sources/bloomer/mapping.d.ts.map +1 -0
  25. package/dist/sources/bloomer/mapping.js +988 -0
  26. package/dist/sources/bloomer/responsive.d.ts +39 -0
  27. package/dist/sources/bloomer/responsive.d.ts.map +1 -0
  28. package/dist/sources/bloomer/responsive.js +259 -0
  29. package/dist/sources/bloomer/specials.d.ts +19 -0
  30. package/dist/sources/bloomer/specials.d.ts.map +1 -0
  31. package/dist/sources/bloomer/specials.js +774 -0
  32. package/dist/sources/bloomer/styles.d.ts +13 -0
  33. package/dist/sources/bloomer/styles.d.ts.map +1 -0
  34. package/dist/sources/bloomer/styles.js +17 -0
  35. package/dist/sources/bloomer/transform.d.ts +27 -0
  36. package/dist/sources/bloomer/transform.d.ts.map +1 -0
  37. package/dist/sources/bloomer/transform.js +826 -0
  38. package/dist/sources/rbx/mapping.d.ts.map +1 -1
  39. package/dist/sources/rbx/mapping.js +20 -18
  40. package/dist/sources/rbx/responsive.d.ts.map +1 -1
  41. package/dist/sources/rbx/responsive.js +1 -31
  42. package/dist/sources/rbx/specials.d.ts.map +1 -1
  43. package/dist/sources/rbx/specials.js +36 -98
  44. package/dist/sources/rbx/transform.d.ts.map +1 -1
  45. package/dist/sources/rbx/transform.js +168 -78
  46. package/dist/sources/react-bulma-components/mapping.js +4 -4
  47. package/dist/sources/react-bulma-components/responsive.d.ts.map +1 -1
  48. package/dist/sources/react-bulma-components/responsive.js +2 -14
  49. package/dist/sources/react-bulma-components/specials.d.ts.map +1 -1
  50. package/dist/sources/react-bulma-components/specials.js +45 -18
  51. package/dist/sources/react-bulma-components/transform.d.ts.map +1 -1
  52. package/dist/sources/react-bulma-components/transform.js +168 -78
  53. package/dist/sources/registry.d.ts.map +1 -1
  54. package/dist/sources/registry.js +2 -0
  55. package/dist/types.d.ts +11 -0
  56. package/dist/types.d.ts.map +1 -1
  57. package/package.json +4 -3
@@ -1 +1 @@
1
- {"version":3,"file":"mapping.d.ts","sourceRoot":"","sources":["../../../src/sources/rbx/mapping.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAuCnE;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAiCtD,CAAC;AAEF,uDAAuD;AACvD,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAOrD,CAAC;AAEF,2DAA2D;AAC3D,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAQvD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAShE,CAAC;AAEF,eAAO,MAAM,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAosBpD,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CA2FhD,CAAC;AAEF,sDAAsD;AACtD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,gBAAgB,GAAG,SAAS,CAM3E"}
1
+ {"version":3,"file":"mapping.d.ts","sourceRoot":"","sources":["../../../src/sources/rbx/mapping.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAuCnE;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAiCtD,CAAC;AAEF,uDAAuD;AACvD,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAOrD,CAAC;AAEF,2DAA2D;AAC3D,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAQvD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAShE,CAAC;AAEF,eAAO,MAAM,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAssBpD,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CA2FhD,CAAC;AAEF,sDAAsD;AACtD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,gBAAgB,GAAG,SAAS,CAM3E"}
@@ -126,7 +126,7 @@ export const MAPPING = {
126
126
  },
127
127
  forwardRefAs: {
128
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',
129
+ todo: '`forwardRefAs` is rbx-internal and has no counterpart. bestax forwards a ref from the form controls and from `Button`, `LinkButton`, `Modal`, `Dropdown`, `Navbar` (plus `Navbar.Burger` and `Navbar.Link`), `Dialog`, `Sidebar`, `Toast` and `Carousel` — pass `ref` directly on those. Mind the `Navbar.Dropdown` collision: your rbx `Navbar.Dropdown` is the menu and becomes `Navbar.DropdownMenu`, which forwards no ref, while bestax `Navbar.Dropdown` — the container your `<Navbar.Item dropdown>` becomes — does. The rest, `Box` included, are plain function components that forward no ref; put the ref on a wrapping element you control',
130
130
  },
131
131
  // ---- elements -----------------------------------------------------------
132
132
  Block: { status: 'mapped', target: 'Block' },
@@ -136,6 +136,7 @@ export const MAPPING = {
136
136
  target: 'Button',
137
137
  props: {
138
138
  as: AS_OK,
139
+ innerRef: { rename: 'ref' },
139
140
  color: {},
140
141
  size: {},
141
142
  outlined: { booleanToProp: { name: 'isOutlined' } },
@@ -497,9 +498,7 @@ export const MAPPING = {
497
498
  managed: {
498
499
  todo: 'bestax `Dropdown` is uncontrolled by default; use `active` + `onActiveChange` for controlled behaviour',
499
500
  },
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
- },
501
+ innerRef: { rename: 'ref' },
503
502
  },
504
503
  subs: {
505
504
  Container: { status: 'mapped', special: 'dropdown-container' },
@@ -606,25 +605,25 @@ export const MAPPING = {
606
605
  props: {
607
606
  active: {},
608
607
  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
- },
608
+ closeOnEsc: { rename: 'closeOnEscape' },
612
609
  closeOnBlur: {
613
- todo: "bestax `Modal` has no built-in background-click close; wire it through `Modal.Background`'s onClick and `onClose`",
610
+ todo: 'bestax `Modal` closes on background click in its legacy form, where it renders its own background wired to `onClose`; a `Modal.Content` or `Modal.Card` child selects the compound form, which renders only the children you wrote — there, add a `<Modal.Background>` whose `onClick` calls your close handler, or drop the prop if you passed false',
614
611
  },
615
612
  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',
613
+ todo: 'bestax `Modal` portals via `portal`, but that takes `true`, a selector or an element — not a `Document`. Pass an element from the document you targeted, or drop the prop if you were passing the default document',
617
614
  },
618
615
  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
- },
616
+ innerRef: { rename: 'ref' },
622
617
  },
623
618
  subs: {
624
619
  Background: { status: 'mapped', target: 'Modal.Background' },
625
620
  Close: { status: 'mapped', target: 'Modal.Close' },
626
621
  Content: { status: 'mapped', target: 'Modal.Content' },
627
- Container: { status: 'mapped', special: 'modal-container' },
622
+ Container: {
623
+ status: 'mapped',
624
+ special: 'modal-container',
625
+ props: { innerRef: { rename: 'ref' } },
626
+ },
628
627
  Card: {
629
628
  status: 'mapped',
630
629
  target: 'Modal.Card',
@@ -641,7 +640,7 @@ export const MAPPING = {
641
640
  },
642
641
  Portal: {
643
642
  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)',
643
+ todo: '`Modal.Portal` is rbx-internal; set `portal` on the `<Modal>` instead — it takes `true`, a selector or an element',
645
644
  },
646
645
  },
647
646
  },
@@ -659,13 +658,15 @@ export const MAPPING = {
659
658
  todo: 'bestax `Navbar` is uncontrolled; drop `managed`',
660
659
  },
661
660
  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
- },
661
+ innerRef: { rename: 'ref' },
665
662
  },
666
663
  subs: {
667
664
  Brand: { status: 'mapped', target: 'Navbar.Brand' },
668
- Burger: { status: 'mapped', target: 'Navbar.Burger' },
665
+ Burger: {
666
+ status: 'mapped',
667
+ target: 'Navbar.Burger',
668
+ props: { innerRef: { rename: 'ref' } },
669
+ },
669
670
  Divider: { status: 'mapped', target: 'Navbar.Divider' },
670
671
  Menu: { status: 'mapped', target: 'Navbar.Menu' },
671
672
  Link: {
@@ -673,6 +674,7 @@ export const MAPPING = {
673
674
  target: 'Navbar.Link',
674
675
  props: {
675
676
  as: AS_OK,
677
+ innerRef: { rename: 'ref' },
676
678
  arrowless: {},
677
679
  onClick: {},
678
680
  },
@@ -1 +1 @@
1
- {"version":3,"file":"responsive.d.ts","sourceRoot":"","sources":["../../../src/sources/rbx/responsive.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,EAML,KAAK,gBAAgB,EACtB,MAAM,yBAAyB,CAAC;AAIjC,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,QAAQ,GAAG,cAAc,CAAC;AAEnE,yEAAyE;AACzE,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAG3D,CAAC;AAwVF,8DAA8D;AAC9D,wBAAgB,sBAAsB,CACpC,GAAG,EAAE,gBAAgB,EACrB,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,EAClB,OAAO,EAAE,GAAG,EACZ,IAAI,EAAE,cAAc,GACnB,IAAI,CAKN"}
1
+ {"version":3,"file":"responsive.d.ts","sourceRoot":"","sources":["../../../src/sources/rbx/responsive.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,EAUL,KAAK,gBAAgB,EACtB,MAAM,yBAAyB,CAAC;AAIjC,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,QAAQ,GAAG,cAAc,CAAC;AAEnE,yEAAyE;AACzE,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAG3D,CAAC;AA0TF,8DAA8D;AAC9D,wBAAgB,sBAAsB,CACpC,GAAG,EAAE,gBAAgB,EACrB,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,EAClB,OAAO,EAAE,GAAG,EACZ,IAAI,EAAE,cAAc,GACnB,IAAI,CAKN"}
@@ -21,42 +21,12 @@
21
21
  * here — only the shape changes.
22
22
  */
23
23
  import { RESPONSIVE_BREAKPOINTS } from './mapping.js';
24
- import { addAttr, addTodo, attributesOf, makeAttr, removeAttr, } from '../_shared/jsx-utils.js';
24
+ import { addAttr, addTodo, attributesOf, attrValue, literalOf, makeAttr, objectExpressionOf, propKey, removeAttr, } from '../_shared/jsx-utils.js';
25
25
  /** Which specials opt their element into the extra breakpoint shapes. */
26
26
  export const RESPONSIVE_KINDS = {
27
27
  column: 'column',
28
28
  'column-group': 'column-group',
29
29
  };
30
- function literalOf(node) {
31
- if (!node)
32
- return undefined;
33
- if (node.type === 'StringLiteral')
34
- return node.value;
35
- if (node.type === 'NumericLiteral')
36
- return node.value;
37
- if (node.type === 'BooleanLiteral')
38
- return node.value;
39
- return undefined;
40
- }
41
- /**
42
- * Reuse a value node as a JSX attribute value: a string literal can sit bare
43
- * (`size="one-third"`), anything else needs an expression container.
44
- */
45
- function attrValue(j, node) {
46
- return node?.type === 'StringLiteral'
47
- ? j.stringLiteral(node.value)
48
- : j.jsxExpressionContainer(node);
49
- }
50
- function propKey(prop) {
51
- const key = prop.key?.name ?? prop.key?.value;
52
- return typeof key === 'string' ? key : undefined;
53
- }
54
- function objectExpressionOf(value) {
55
- return value?.type === 'JSXExpressionContainer' &&
56
- value.expression?.type === 'ObjectExpression'
57
- ? value.expression
58
- : null;
59
- }
60
30
  /**
61
31
  * One `{ value, only }` cell of the universal `responsive` prop. Returns the
62
32
  * inner `value` node, or undefined when the cell isn't the expected shape.
@@ -1 +1 @@
1
- {"version":3,"file":"specials.d.ts","sourceRoot":"","sources":["../../../src/sources/rbx/specials.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAO3C,OAAO,EAYL,KAAK,gBAAgB,EACtB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAML,KAAK,aAAa,EACnB,MAAM,8BAA8B,CAAC;AAEtC,YAAY,EAAE,aAAa,EAAE,CAAC;AAy8B9B,wBAAgB,UAAU,CACxB,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,gBAAgB,EACrB,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,EAClB,OAAO,EAAE,GAAG,GACX,aAAa,CAIf"}
1
+ {"version":3,"file":"specials.d.ts","sourceRoot":"","sources":["../../../src/sources/rbx/specials.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAO3C,OAAO,EAYL,KAAK,gBAAgB,EACtB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAUL,KAAK,aAAa,EACnB,MAAM,8BAA8B,CAAC;AAEtC,YAAY,EAAE,aAAa,EAAE,CAAC;AAs2B9B,wBAAgB,UAAU,CACxB,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,gBAAgB,EACrB,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,EAClB,OAAO,EAAE,GAAG,GACX,aAAa,CAIf"}
@@ -15,26 +15,9 @@
15
15
  */
16
16
  import { BADGE_PROPS, RESPONSIVE_BREAKPOINTS, TOOLTIP_PROPS, UNIVERSAL_PROPS, } from './mapping.js';
17
17
  import { addAttr, addTodo, buildJsxName, attributesOf, findAttr, jsxNameParts, literalValueOf, makeAttr, plainElement, removeAttr, resolveBooleanish, } from '../_shared/jsx-utils.js';
18
- import { alignTarget, makeStripModifierProps, mergeClassName, parseIconClasses, } from '../_shared/specials-utils.js';
18
+ import { alignTarget, applyIconProps, makeStripModifierProps, makeStructuralHelpers, mergeClassName, modifierClass, parseIconClasses, restrictAsToTargets, } from '../_shared/specials-utils.js';
19
19
  /* eslint-disable @typescript-eslint/no-explicit-any */
20
20
  const stripModifierProps = makeStripModifierProps(UNIVERSAL_PROPS, RESPONSIVE_BREAKPOINTS);
21
- /**
22
- * Turn a literal modifier prop into an `is-*` class fragment for an element
23
- * on its way to becoming plain HTML. Returns the class (or undefined) and
24
- * always removes the attribute.
25
- */
26
- function modifierClass(ctx, path, element, prop, base, where) {
27
- const attr = findAttr(element, prop);
28
- if (!attr)
29
- return base;
30
- const literal = literalValueOf(attr);
31
- removeAttr(element, attr);
32
- if (literal.kind === 'string' || literal.kind === 'number') {
33
- return `${base} is-${literal.value}`;
34
- }
35
- addTodo(ctx, path, `prop:${prop}`, `dynamic ${where} \`${prop}\`; add the matching is-* class by hand`);
36
- return base;
37
- }
38
21
  /**
39
22
  * Also strips the universal `responsive` object: responsive.ts would have
40
23
  * consumed it, but a `replaced: true` special skips that pass too, and it is
@@ -68,66 +51,11 @@ function stripHelperComponentProps(ctx, path, attrs, where) {
68
51
  return kept;
69
52
  }
70
53
  /**
71
- * rbx puts `as` on every component, but a handler that chooses its target from
72
- * a prop value can land on a bestax component that has no `as` — Media.Item
73
- * resolves to Left/Content/Right where only Left declares one, Level.Item to
74
- * Left/Right/Item where only Item does, Navbar.Item to Item/Dropdown where
75
- * only Item does. Opting into `as` in the mapping table cannot express that,
76
- * so the target has to be checked after it is picked.
77
- */
78
- function restrictAsToTargets(ctx, path, element, target, allowed) {
79
- const asAttr = findAttr(element, 'as');
80
- if (!asAttr || (target && allowed.includes(target)))
81
- return;
82
- removeAttr(element, asAttr);
83
- addTodo(ctx, path, 'prop:as', `bestax's \`${target}\` has no \`as\` prop (of the targets this can resolve to, only ${allowed
84
- .map(a => `\`${a}\``)
85
- .join(' / ')} does); render the tag directly or restructure`);
86
- ctx.dirty = true;
87
- }
88
- /** Replace the element with a plain HTML tag carrying `className`. */
89
- function replaceWithPlain(ctx, path, element, tag, className, where) {
90
- const merged = mergeClassName(ctx, path, element, className, where);
91
- const rest = stripHelperComponentProps(ctx, path, stripModifierProps(ctx, path, attributesOf(element), where), where);
92
- path.replace(plainElement(ctx.j, tag, merged, rest, element.children ?? []));
93
- ctx.dirty = true;
94
- return { replaced: true };
95
- }
96
- /**
97
- * rbx wraps several components in a `*.Container` that owns the modifiers,
98
- * where bestax's component renders that wrapper itself. Move the container's
99
- * (already-converted) attributes onto its single child and replace the
100
- * container with it — otherwise both would rename to the same bestax
101
- * component and nest, which is invalid.
102
- *
103
- * Returns null when the element isn't the single-JSX-child shape, so the
104
- * caller can fall back to keeping the container.
54
+ * rbx's plain-element rewrites strip both the universal helper props and the
55
+ * badge/tooltip/responsive props (which would otherwise ride onto the HTML
56
+ * tag as unknown DOM attributes — see stripHelperComponentProps).
105
57
  */
106
- function collapseOntoChild(ctx, path, element, where, expected) {
107
- const children = (element.children ?? []).filter((c) => !(c.type === 'JSXText' && c.value.trim() === ''));
108
- if (children.length !== 1 || children[0].type !== 'JSXElement')
109
- return null;
110
- const child = children[0];
111
- // The child must be the component this container exists to wrap. rbx lets
112
- // both of these containers hold something else — an `<iframe>` for a ratio
113
- // box, a native `<select>` — and folding onto that put Bulma modifier props
114
- // on an intrinsic element and dropped the wrapper the markup needs.
115
- const childPath = ctx.resolve?.(child.openingElement?.name);
116
- if (!childPath || childPath.join('.') !== expected)
117
- return null;
118
- for (const attr of [...attributesOf(element)]) {
119
- const name = attr.name.name;
120
- if (findAttr(child, name)) {
121
- addTodo(ctx, path, `prop:${name}`, `\`${where}\` folded into its child, but both set \`${name}\`; the child's value was kept — reconcile by hand`);
122
- continue;
123
- }
124
- removeAttr(element, attr);
125
- addAttr(child, attr);
126
- }
127
- path.replace(child);
128
- ctx.dirty = true;
129
- return { replaced: true };
130
- }
58
+ const { replaceWithPlain, collapseOntoChild } = makeStructuralHelpers((ctx, path, attrs, where) => stripHelperComponentProps(ctx, path, stripModifierProps(ctx, path, attrs, where), where));
131
59
  const SPECIALS = {
132
60
  /**
133
61
  * rbx's `Heading` is Bulma's `.heading` label (small caps), not a title —
@@ -171,6 +99,11 @@ const SPECIALS = {
171
99
  else if (resolved === 'falsy') {
172
100
  removeAttr(element, spacedAttr);
173
101
  }
102
+ else if (findAttr(element, 'isSpaced')) {
103
+ // `isSpaced` is already set (rbx passes unknown props through), so
104
+ // renaming onto it would emit the attribute twice.
105
+ addTodo(ctx, path, 'prop:spaced', 'dynamic `spaced` alongside an `isSpaced` already on this element; bestax has one prop for both — reconcile by hand');
106
+ }
174
107
  else {
175
108
  // Dynamic: keep the expression, just rename the prop.
176
109
  spacedAttr.name = ctx.j.jsxIdentifier('isSpaced');
@@ -195,20 +128,10 @@ const SPECIALS = {
195
128
  const literal = classAttr ? literalValueOf(classAttr) : undefined;
196
129
  if (literal?.kind === 'string') {
197
130
  const parsed = parseIconClasses(literal.value);
198
- if (parsed) {
199
- addAttr(element, makeAttr(ctx.j, 'name', parsed.name));
200
- if (parsed.library) {
201
- addAttr(element, makeAttr(ctx.j, 'library', parsed.library));
202
- }
203
- if (parsed.variant) {
204
- addAttr(element, makeAttr(ctx.j, 'variant', parsed.variant));
205
- }
206
- element.children = [];
207
- if (element.openingElement) {
208
- element.openingElement.selfClosing = true;
209
- element.closingElement = null;
210
- }
211
- ctx.dirty = true;
131
+ // An app's own class on the <i> has no home on bestax's glyph, so
132
+ // the child is kept (and flagged below) rather than losing it.
133
+ if (parsed && parsed.leftovers.length === 0) {
134
+ applyIconProps(ctx, path, element, parsed);
212
135
  return {};
213
136
  }
214
137
  }
@@ -538,15 +461,14 @@ const SPECIALS = {
538
461
  return { ...result, handledProps: ['align', 'as'] };
539
462
  },
540
463
  /**
541
- * The markup maps cleanly, but three rbx behaviours do not exist in bestax
542
- * and none of them fail loudly: rbx portals the modal into `document.body`,
543
- * closes it on Escape by default, and clips document scroll while it is
544
- * open. bestax renders inline and implements none of the three. An
545
- * unmodified `<Modal>` therefore migrates to something that looks right and
546
- * behaves differently, so every conversion is flagged.
464
+ * rbx's Modal portals into `document.body`, closes on Escape and clips
465
+ * document scroll, all by default. bestax matches two of those since #633 —
466
+ * `closeOnEscape` and `lockScroll` both default to `true` — but still
467
+ * renders inline unless `portal` is set. That last difference does not fail
468
+ * loudly, so every conversion is still flagged, now about the portal alone.
547
469
  */
548
470
  modal(ctx, path, _element) {
549
- addTodo(ctx, path, 'component:Modal', 'bestax `Modal` renders inline rather than portalling into document.body, and implements neither Escape-to-close nor scroll locking — rbx did all three by default. Re-add whichever your UI relied on');
471
+ addTodo(ctx, path, 'component:Modal', 'bestax `Modal` closes on Escape and locks body scroll by default, matching rbx, but it renders inline: set `portal` if you relied on rbx portalling into document.body');
550
472
  return {};
551
473
  },
552
474
  /** rbx Modal.Container is the modal itself in bestax. */
@@ -588,6 +510,21 @@ const SPECIALS = {
588
510
  addTodo(ctx, path, `prop:${prop}`, `bestax \`Navbar.Item\` has no \`${prop}\` prop; restructure or add a class by hand`);
589
511
  ctx.dirty = true;
590
512
  }
513
+ // bestax's `Navbar.Dropdown` forwards a ref, but it is the one such target
514
+ // the `innerRef: { rename: 'ref' }` entries in mapping.ts cannot reach:
515
+ // that table is keyed on the rbx name (`Navbar.Item`), and only this
516
+ // handler knows which of the two targets was picked. A plain
517
+ // `Navbar.Item` is still a function component, so the rename is
518
+ // conditional — there, `innerRef` is left alone.
519
+ const renamedInnerRef = [];
520
+ if (target === 'Navbar.Dropdown') {
521
+ const innerRefAttr = findAttr(element, 'innerRef');
522
+ if (innerRefAttr) {
523
+ innerRefAttr.name = ctx.j.jsxIdentifier('ref');
524
+ renamedInnerRef.push('innerRef');
525
+ ctx.dirty = true;
526
+ }
527
+ }
591
528
  restrictAsToTargets(ctx, path, element, target ?? 'Navbar.Item', [
592
529
  'Navbar.Item',
593
530
  ]);
@@ -601,6 +538,7 @@ const SPECIALS = {
601
538
  'expanded',
602
539
  'hoverable',
603
540
  'managed',
541
+ ...renamedInnerRef,
604
542
  ],
605
543
  };
606
544
  },
@@ -1 +1 @@
1
- {"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../../../src/sources/rbx/transform.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AA0DvD,MAAM,CAAC,OAAO,UAAU,SAAS,CAC/B,QAAQ,EAAE,QAAQ,EAClB,GAAG,EAAE,GAAG,EACR,OAAO,GAAE,gBAAqB,GAC7B,MAAM,GAAG,SAAS,CAu/BpB"}
1
+ {"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../../../src/sources/rbx/transform.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AA2DvD,MAAM,CAAC,OAAO,UAAU,SAAS,CAC/B,QAAQ,EAAE,QAAQ,EAClB,GAAG,EAAE,GAAG,EACR,OAAO,GAAE,gBAAqB,GAC7B,MAAM,GAAG,SAAS,CAmoCpB"}