bestax-migrate 2.27.0 → 2.29.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.
@@ -4,7 +4,8 @@
4
4
  * Unlike the library sources there is no import to anchor on: a file is in
5
5
  * scope when a lowercase JSX element carries a `className` with a Bulma class
6
6
  * `class-map.ts` knows. Each such element goes through `plan()`, which
7
- * converts it only when the component renders exactly the same markup.
7
+ * converts it only when the component renders exactly the same markup, but
8
+ * for the one exception it states (Font Awesome's hidden `<i>`).
8
9
  *
9
10
  * Passes, in order:
10
11
  * 1. the shared stylesheet-import pass (a no-op under the default `keep`)
@@ -21,9 +22,10 @@ import { addTodo, findAttr, forgetJsxParens, jsxNameParts, literalValueOf, makeA
21
22
  import { collectBoundNames, makeReserve, prefersTabs, resolvesToBinding, } from '../_shared/imports.js';
22
23
  import { rewriteStylesheetImports } from '../_shared/css-imports.js';
23
24
  import { BESTAX, buildBestaxImport, placeBestaxImport, seedBestaxImport, } from '../_shared/bestax-import.js';
25
+ import { glyphProps } from './glyph.js';
24
26
  import { fileContextTodo, plan, } from './plan.js';
25
27
  import { REACT_RUNTIMES, } from './project.js';
26
- import { PLACED, placedFor, ROOTS, rootFor } from './class-map.js';
28
+ import { PLACED, placedFor, ROOTS, rootFor, WRAPPERS, } from './class-map.js';
27
29
  import { ruleId } from './rules.js';
28
30
  /**
29
31
  * A part imported under its flat export (`MenuList`, `FieldLabel`) is the
@@ -103,13 +105,25 @@ function spliceClassName(element, replacement, carried = []) {
103
105
  }
104
106
  attrs.splice(attrs.indexOf(classAttr), 1, ...replacement);
105
107
  }
106
- /** Put `props`, then what stays in `className`, where `className` was. */
107
- function writeClassName(j, element, props, className) {
108
- const replacement = props.map(([name, value]) => makeAttr(j, name, value === true ? undefined : value));
108
+ /**
109
+ * Put `lead`, then `props`, then what stays in `className`, where
110
+ * `className` was.
111
+ */
112
+ function writeClassName(j, element, props, className, lead = []) {
113
+ const replacement = [
114
+ ...lead,
115
+ ...props.map(([name, value]) => makeAttr(j, name, value === true ? undefined : value)),
116
+ ];
109
117
  if (className)
110
118
  replacement.push(makeAttr(j, 'className', className));
111
119
  spliceClassName(element, replacement);
112
120
  }
121
+ /** A glyph's props as JSX attributes, a list of features as an array. */
122
+ function glyphAttributes(j, glyph) {
123
+ return glyphProps(glyph).map(([name, value]) => typeof value === 'string'
124
+ ? makeAttr(j, name, value)
125
+ : j.jsxAttribute(j.jsxIdentifier(name), j.jsxExpressionContainer(propValue(j, value, false))));
126
+ }
113
127
  /**
114
128
  * The same, for a `className` a joiner builds: the classes a prop now
115
129
  * renders leave the call, each condition that became a prop moves onto it,
@@ -117,7 +131,7 @@ function writeClassName(j, element, props, className) {
117
131
  * conditional, no `className` when nothing is left). Returns whether the
118
132
  * call is gone.
119
133
  */
120
- function writeJoined(j, element, conversion, joined) {
134
+ function writeJoined(j, element, conversion, joined, lead = []) {
121
135
  const { call, statics, parts } = joined;
122
136
  const kept = new Set(conversion.className?.split(' ') ?? []);
123
137
  const carried = [];
@@ -164,6 +178,7 @@ function writeJoined(j, element, conversion, joined) {
164
178
  classes.length > 0 ? [makeAttr(j, 'className', classes.join(' '))] : [];
165
179
  }
166
180
  spliceClassName(element, [
181
+ ...lead,
167
182
  ...conversion.props.map(([name, value]) => makeAttr(j, name, value === true ? undefined : value)),
168
183
  ...conditions,
169
184
  ...rest,
@@ -734,6 +749,83 @@ function staticContent(element) {
734
749
  child.expression.type === 'StringLiteral' &&
735
750
  child.expression.value !== '')));
736
751
  }
752
+ /** The static classes of a plain HTML element, or undefined. */
753
+ function htmlTokens(element) {
754
+ const name = element?.openingElement?.name;
755
+ if (name?.type !== 'JSXIdentifier' || !INTRINSIC.test(name.name)) {
756
+ return undefined;
757
+ }
758
+ const classAttr = findAttr(element, 'className');
759
+ const className = classAttr ? staticClassName(classAttr) : '';
760
+ return className === null
761
+ ? undefined
762
+ : className.split(/\s+/).filter(Boolean);
763
+ }
764
+ /** Whether a component builds content from props rather than children. */
765
+ function buildsContent(entry) {
766
+ return (!!entry &&
767
+ (!!entry.buildsIcons ||
768
+ !!entry.buildsFile ||
769
+ entry.rendersText !== undefined ||
770
+ !!entry.countsChildren ||
771
+ !!entry.absorbs));
772
+ }
773
+ /**
774
+ * What names a link or button, as `ElementFacts.clickableAround` says, with
775
+ * `self` left out of its content: its own `aria-labelledby` or `aria-label`,
776
+ * written out and not empty, else static text in it that is not inside an
777
+ * icon or hidden, else nothing. Undefined when it can't be told: a name or
778
+ * content written as an expression, a `title`, or something inside that may
779
+ * name it some other way (`kindOf` says which, or an `alt`, an `<svg>` or a
780
+ * label of its own).
781
+ */
782
+ function labelOf(clickable, self, kindOf) {
783
+ if (findAttr(clickable, 'title'))
784
+ return undefined;
785
+ for (const name of ['aria-labelledby', 'aria-label']) {
786
+ const attr = findAttr(clickable, name);
787
+ if (!attr)
788
+ continue;
789
+ const value = attributeValue(attr);
790
+ if (value === null)
791
+ return undefined;
792
+ if (typeof value === 'string' && value.trim() !== '')
793
+ return name;
794
+ }
795
+ let unsure = false;
796
+ const named = (node) => (node.children ?? []).some((child) => {
797
+ if (child === self || dropped(child))
798
+ return false;
799
+ if (child.type === 'JSXText') {
800
+ return /\S/.test(jsxTextRenders(child.value));
801
+ }
802
+ if (child.type === 'JSXFragment')
803
+ return named(child);
804
+ if (child.type !== 'JSXElement') {
805
+ if (isContent(child))
806
+ unsure = true;
807
+ return false;
808
+ }
809
+ const kind = kindOf(child);
810
+ if (kind === 'icon')
811
+ return false;
812
+ if (kind === 'opaque') {
813
+ unsure = true;
814
+ return false;
815
+ }
816
+ const hidden = findAttr(child, 'aria-hidden');
817
+ if (hidden && attributeValue(hidden) === 'true')
818
+ return false;
819
+ if (['alt', 'title', 'aria-label', 'aria-labelledby'].some(name => findAttr(child, name)) ||
820
+ child.openingElement.name.name === 'svg') {
821
+ unsure = true;
822
+ }
823
+ return named(child);
824
+ });
825
+ if (named(clickable))
826
+ return 'text';
827
+ return unsure ? undefined : 'none';
828
+ }
737
829
  /**
738
830
  * An element's content as one prop value: its one element, or its text, or
739
831
  * a fragment of them all, each text written as the string it renders.
@@ -1012,6 +1104,121 @@ export default function transform(fileInfo, api, options = {}) {
1012
1104
  readings.set(child, reading);
1013
1105
  return reading;
1014
1106
  };
1107
+ // A bestax component already in the file is read as the markup the table
1108
+ // says it renders, so a TODO that reads what is around an element says the
1109
+ // same once the elements around it have converted.
1110
+ /** The table's entry for a bestax component, with its root class. */
1111
+ const entryFor = (target) => {
1112
+ const root = target &&
1113
+ Object.keys(ROOTS).find(key => ROOTS[key].status === 'mapped' && ROOTS[key].target === target);
1114
+ return root ? { root, entry: ROOTS[root] } : undefined;
1115
+ };
1116
+ /**
1117
+ * The classes an element renders, as far as the file shows: a plain HTML
1118
+ * element's static ones, or a bestax component's root class and the flags
1119
+ * written out as `true` (`<Control hasIconsLeft>` as `control
1120
+ * has-icons-left`).
1121
+ */
1122
+ const renderedTokens = (path) => {
1123
+ const html = htmlTokens(path.node);
1124
+ if (html)
1125
+ return html;
1126
+ const found = entryFor(bestaxTarget(path.node, path));
1127
+ if (!found)
1128
+ return undefined;
1129
+ const flags = Object.entries(found.entry.modifiers ?? {})
1130
+ .filter(([, modifier]) => {
1131
+ const [write, ...more] = modifier.writes;
1132
+ const attr = write && more.length === 0 && write.value === undefined
1133
+ ? findAttr(path.node, write.prop)
1134
+ : undefined;
1135
+ return attr && attributeValue(attr) === true;
1136
+ })
1137
+ .map(([token]) => token);
1138
+ return [found.root, ...flags];
1139
+ };
1140
+ /**
1141
+ * The tag an element renders: its own, or a bestax component's, from a
1142
+ * static `as` or the table; an empty string for a bestax component whose
1143
+ * tag the table doesn't give, and undefined for any other component.
1144
+ */
1145
+ const renderedTag = (path) => {
1146
+ const name = path.node.openingElement.name;
1147
+ if (name.type === 'JSXIdentifier' && INTRINSIC.test(name.name)) {
1148
+ return name.name;
1149
+ }
1150
+ const target = bestaxTarget(path.node, path);
1151
+ if (!target)
1152
+ return undefined;
1153
+ const as = findAttr(path.node, 'as');
1154
+ if (as) {
1155
+ const value = attributeValue(as);
1156
+ return typeof value === 'string' ? value : undefined;
1157
+ }
1158
+ return (entryFor(target)?.entry.tag ??
1159
+ Object.keys(WRAPPERS).find(tag => WRAPPERS[tag] === target) ??
1160
+ '');
1161
+ };
1162
+ /**
1163
+ * Where an element whose target renders a glyph sits, for its TODO: the
1164
+ * classes the element directly around it renders, and the nearest <a> or
1165
+ * <button> around it, reached through markup and bestax components only,
1166
+ * since any other component between could render anything, with what
1167
+ * names that.
1168
+ */
1169
+ const glyphPlace = (elementPath) => {
1170
+ const parent = elementPath.parent;
1171
+ const parentTokens = parent?.node?.type === 'JSXElement' ? renderedTokens(parent) : undefined;
1172
+ let clickable;
1173
+ let tag;
1174
+ for (let up = elementPath.parent; up && !clickable; up = up.parent) {
1175
+ if (up.node?.type !== 'JSXElement')
1176
+ continue;
1177
+ tag = renderedTag(up);
1178
+ if (tag === undefined)
1179
+ break;
1180
+ if (tag === 'a' || tag === 'button')
1181
+ clickable = up;
1182
+ }
1183
+ const kindOf = (element) => {
1184
+ const tokens = htmlTokens(element);
1185
+ const entries = tokens
1186
+ ? tokens.map(token => rootFor(token)).filter(Boolean)
1187
+ : [entryFor(bestaxTarget(element, clickable))?.entry];
1188
+ if (!tokens && !bestaxTarget(element, clickable))
1189
+ return 'opaque';
1190
+ if (entries.some(entry => entry?.readsGlyph))
1191
+ return 'icon';
1192
+ return entries.some(buildsContent) ? 'opaque' : 'open';
1193
+ };
1194
+ const label = clickable && labelOf(clickable.node, elementPath.node, kindOf);
1195
+ return {
1196
+ ...(parentTokens && { parentTokens }),
1197
+ ...(clickable && {
1198
+ clickableAround: { tag: tag, ...(label && { label }) },
1199
+ }),
1200
+ };
1201
+ };
1202
+ /**
1203
+ * An element that goes with the one around it, which renders it from
1204
+ * props, converts no more on its own, and nor does it when that one stays
1205
+ * markup for a person: a re-run has to find the markup it found. Only its
1206
+ * TODOs stay. It has a class to be found by, so it has been planned.
1207
+ */
1208
+ const forget = (node) => {
1209
+ const own = { conversion: null, todos: planned.get(node).todos };
1210
+ planned.set(node, own);
1211
+ readings.delete(node);
1212
+ const at = elements.findIndex(found => found.path.node === node);
1213
+ if (at === -1)
1214
+ return;
1215
+ if (own.todos.length > 0) {
1216
+ elements[at] = { ...elements[at], plan: own, converts: false };
1217
+ }
1218
+ else {
1219
+ elements.splice(at, 1);
1220
+ }
1221
+ };
1015
1222
  /** The element directly around this one, when this is its only child. */
1016
1223
  const holderOf = (elementPath) => {
1017
1224
  const up = elementPath.parent?.node;
@@ -1065,6 +1272,9 @@ export default function transform(fileInfo, api, options = {}) {
1065
1272
  }),
1066
1273
  ...(textOf(element) !== undefined && { text: textOf(element) }),
1067
1274
  };
1275
+ if (facts.tokens.some(token => rootFor(token)?.readsGlyph)) {
1276
+ Object.assign(facts, glyphPlace(elementPath));
1277
+ }
1068
1278
  // A joiner call the codemod can read converts exactly: its fixed classes
1069
1279
  // as for a static className, and each condition on a flag as its prop.
1070
1280
  const joined = className === null
@@ -1085,6 +1295,7 @@ export default function transform(fileInfo, api, options = {}) {
1085
1295
  }
1086
1296
  let result = exact ?? plan(facts);
1087
1297
  const builds = result.conversion?.icons;
1298
+ const glyphs = result.conversion?.glyph !== undefined;
1088
1299
  const converts = result.conversion !== null || result.fold !== undefined;
1089
1300
  const becomes = result.conversion?.target ?? result.fold?.target;
1090
1301
  if (className === null && !exact && becomes) {
@@ -1117,27 +1328,14 @@ export default function transform(fileInfo, api, options = {}) {
1117
1328
  inner.numbers.push(...result.fold.numbers);
1118
1329
  }
1119
1330
  if (builds) {
1120
- // A `.icon` the target builds from props goes with the element, so it
1121
- // doesn't convert on its own, and nor does it when a computed
1122
- // `className` leaves the element to a person: a re-run has to find
1123
- // the markup it found.
1331
+ // A `.icon` the target builds from props goes with the element.
1124
1332
  const inside = (element.children ?? []).filter((child) => !dropped(child));
1125
- for (const icon of builds) {
1126
- const node = inside[icon.index];
1127
- const own = { conversion: null, todos: planned.get(node).todos };
1128
- planned.set(node, own);
1129
- readings.delete(node);
1130
- const at = elements.findIndex(found => found.path.node === node);
1131
- if (at === -1)
1132
- continue;
1133
- if (own.todos.length > 0) {
1134
- elements[at] = { ...elements[at], plan: own, converts: false };
1135
- }
1136
- else {
1137
- elements.splice(at, 1);
1138
- }
1139
- }
1333
+ for (const icon of builds)
1334
+ forget(inside[icon.index]);
1140
1335
  }
1336
+ // So does the <i> a glyph the target renders takes the place of.
1337
+ if (glyphs)
1338
+ forget(sole);
1141
1339
  planned.set(element, result);
1142
1340
  if (result.conversion || result.fold || result.todos.length > 0) {
1143
1341
  elements.unshift({ path: elementPath, plan: result, converts });
@@ -1313,16 +1511,14 @@ export default function transform(fileInfo, api, options = {}) {
1313
1511
  }
1314
1512
  }
1315
1513
  // A target that renders the element's children itself takes their place:
1316
- // they go, and so does the closing tag, their comments and its own moving
1317
- // to the name that stays.
1514
+ // they go, and so does the closing tag, their comments (in their tags
1515
+ // and attributes too: a glyph's <i> has a `className`) and its own
1516
+ // moving to the name that stays.
1318
1517
  const gone = conversion.rendersChildren
1319
1518
  ? [
1320
1519
  ...(element.children ?? [])
1321
1520
  .filter((node) => node.type === 'JSXElement')
1322
- .flatMap((node) => [
1323
- ...(node.comments ?? []),
1324
- ...tagComments(node),
1325
- ]),
1521
+ .flatMap((node) => commentsWithin(node)),
1326
1522
  ...[element.closingElement, element.closingElement?.name].flatMap((node) => node?.comments ?? []),
1327
1523
  ]
1328
1524
  : [];
@@ -1333,10 +1529,12 @@ export default function transform(fileInfo, api, options = {}) {
1333
1529
  removeAttr(holder, findAttr(holder, name));
1334
1530
  }
1335
1531
  const joined = joins.get(element);
1532
+ // A glyph the target renders goes first, as the planner wrote it.
1533
+ const lead = conversion.glyph ? glyphAttributes(j, conversion.glyph) : [];
1336
1534
  if (!joined) {
1337
- writeClassName(j, element, conversion.props, conversion.className);
1535
+ writeClassName(j, element, conversion.props, conversion.className, lead);
1338
1536
  }
1339
- else if (writeJoined(j, element, conversion, joined)) {
1537
+ else if (writeJoined(j, element, conversion, joined, lead)) {
1340
1538
  unjoined.add(joined.call.callee.name);
1341
1539
  }
1342
1540
  if (conversion.rendersChildren) {
@@ -13,6 +13,16 @@
13
13
  * explicit conversions rather than guesses.
14
14
  */
15
15
  import type { ComponentMapping, PropAction } from '../../types.js';
16
+ /**
17
+ * Components whose bestax counterpart really does accept `as`. An empty
18
+ * action passes the prop through untouched; its job is to claim the name so
19
+ * the universal `AS_TODO` never sees it.
20
+ *
21
+ * Several of these narrow `as` to a literal union (`Footer` is
22
+ * `'footer' | 'div'`, `Control` is `'div'`), so an `as={SomeComponent}` still
23
+ * surfaces — as a type error the user can see, rather than a silent rewrite.
24
+ */
25
+ export declare const AS_OK: PropAction;
16
26
  /**
17
27
  * Helper props rbx mixes into every component via `HelpersProps`. Applied
18
28
  * after each component's own prop map, to whatever attributes are left.
@@ -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;AA+BnE;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CA4CtD,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,CAitBpD,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;AAoBnE;;;;;;;;GAQG;AACH,eAAO,MAAM,KAAK,EAAE,UAAe,CAAC;AAEpC;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CA4CtD,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,CAutBpD,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"}
@@ -18,7 +18,7 @@ const DOCS = 'https://bestax.io/docs';
18
18
  const numeric = { numberToString: true };
19
19
  /**
20
20
  * `as` is universal in rbx — `forwardRefAs` puts it on every component — but
21
- * bestax declares it on only ~16 components, several of them constrained to a
21
+ * bestax declares it on only some components, several of them constrained to a
22
22
  * narrow literal union (`Footer` is `'footer' | 'div'`). So the default is a
23
23
  * TODO, and the components that really do accept `as` opt in with `AS_OK` in
24
24
  * their own prop map, which runs first and wins.
@@ -36,7 +36,7 @@ const AS_TODO = {
36
36
  * `'footer' | 'div'`, `Control` is `'div'`), so an `as={SomeComponent}` still
37
37
  * surfaces — as a type error the user can see, rather than a silent rewrite.
38
38
  */
39
- const AS_OK = {};
39
+ export const AS_OK = {};
40
40
  /**
41
41
  * Helper props rbx mixes into every component via `HelpersProps`. Applied
42
42
  * after each component's own prop map, to whatever attributes are left.
@@ -182,6 +182,7 @@ export const MAPPING = {
182
182
  status: 'mapped',
183
183
  target: 'Container',
184
184
  props: {
185
+ as: AS_OK,
185
186
  fluid: {},
186
187
  // bestax's ContainerBreakpoint is 'tablet' | 'desktop' | 'widescreen';
187
188
  // rbx accepts all six, so the other three are type errors if passed on.
@@ -729,10 +730,14 @@ export const MAPPING = {
729
730
  Panel: {
730
731
  status: 'mapped',
731
732
  target: 'Panel',
732
- props: { color: {} },
733
+ props: { as: AS_OK, color: {} },
733
734
  subs: {
734
735
  Heading: { status: 'mapped', target: 'Panel.Heading' },
735
- Block: { status: 'mapped', target: 'Panel.Block', props: { active: {} } },
736
+ Block: {
737
+ status: 'mapped',
738
+ target: 'Panel.Block',
739
+ props: { active: {}, as: AS_OK },
740
+ },
736
741
  Icon: { status: 'partial', target: 'Panel.Icon', special: 'icon' },
737
742
  Tab: {
738
743
  status: 'mapped',
@@ -815,6 +820,7 @@ export const MAPPING = {
815
820
  status: 'mapped',
816
821
  target: 'Hero',
817
822
  props: {
823
+ as: AS_OK,
818
824
  color: {},
819
825
  // rbx's five sizes are bestax's five, `fullheight-with-navbar`
820
826
  // included, and both render the one `is-<size>` class.
@@ -825,8 +831,8 @@ export const MAPPING = {
825
831
  },
826
832
  subs: {
827
833
  Body: { status: 'mapped', target: 'Hero.Body' },
828
- Foot: { status: 'mapped', target: 'Hero.Foot' },
829
- Head: { status: 'mapped', target: 'Hero.Head' },
834
+ Foot: { status: 'mapped', target: 'Hero.Foot', props: { as: AS_OK } },
835
+ Head: { status: 'mapped', target: 'Hero.Head', props: { as: AS_OK } },
830
836
  },
831
837
  },
832
838
  Section: { status: 'mapped', target: 'Section', props: { size: {} } },
@@ -1 +1 @@
1
- {"version":3,"file":"mapping.d.ts","sourceRoot":"","sources":["../../../src/sources/react-bulma-components/mapping.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAQnE;;;;GAIG;AACH,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAgFtD,CAAC;AAEF,gEAAgE;AAChE,eAAO,MAAM,sBAAsB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAUhE,CAAC;AAIF,eAAO,MAAM,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAmqBpD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAsEhD,CAAC;AAEF,6EAA6E;AAC7E,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,gBAAgB,GAAG,SAAS,CAM3E"}
1
+ {"version":3,"file":"mapping.d.ts","sourceRoot":"","sources":["../../../src/sources/react-bulma-components/mapping.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAQnE;;;;GAIG;AACH,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAgFtD,CAAC;AAEF,gEAAgE;AAChE,eAAO,MAAM,sBAAsB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAUhE,CAAC;AAIF,eAAO,MAAM,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAirBpD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAsEhD,CAAC;AAEF,6EAA6E;AAC7E,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,gBAAgB,GAAG,SAAS,CAM3E"}
@@ -252,6 +252,7 @@ export const MAPPING = {
252
252
  status: 'mapped',
253
253
  target: 'Container',
254
254
  props: {
255
+ renderAs: { rename: 'as' },
255
256
  max: { booleanToProp: { name: 'isMax' } },
256
257
  breakpoint: {
257
258
  // 'tablet' / 'desktop' stay as breakpoint="..." (same prop in bestax)
@@ -430,6 +431,7 @@ export const MAPPING = {
430
431
  status: 'mapped',
431
432
  target: 'Hero',
432
433
  props: {
434
+ renderAs: { rename: 'as' },
433
435
  color: {},
434
436
  size: {
435
437
  valueTodo: {
@@ -442,9 +444,17 @@ export const MAPPING = {
442
444
  hasNavbar: { booleanToProp: { name: 'fullheightWithNavbar' } },
443
445
  },
444
446
  subs: {
445
- Header: { status: 'mapped', target: 'Hero.Head' },
447
+ Header: {
448
+ status: 'mapped',
449
+ target: 'Hero.Head',
450
+ props: { renderAs: { rename: 'as' } },
451
+ },
446
452
  Body: { status: 'mapped', target: 'Hero.Body' },
447
- Footer: { status: 'mapped', target: 'Hero.Foot' },
453
+ Footer: {
454
+ status: 'mapped',
455
+ target: 'Hero.Foot',
456
+ props: { renderAs: { rename: 'as' } },
457
+ },
448
458
  },
449
459
  },
450
460
  Icon: {
@@ -688,7 +698,7 @@ export const MAPPING = {
688
698
  Panel: {
689
699
  status: 'mapped',
690
700
  target: 'Panel',
691
- props: { color: {} },
701
+ props: { renderAs: { rename: 'as' }, color: {} },
692
702
  subs: {
693
703
  Header: { status: 'mapped', target: 'Panel.Heading' },
694
704
  Tabs: {
@@ -699,7 +709,11 @@ export const MAPPING = {
699
709
  Tab: { status: 'mapped', special: 'panel-tab' },
700
710
  },
701
711
  },
702
- Block: { status: 'mapped', target: 'Panel.Block', props: activeKept },
712
+ Block: {
713
+ status: 'mapped',
714
+ target: 'Panel.Block',
715
+ props: { ...activeKept, renderAs: { rename: 'as' } },
716
+ },
703
717
  Icon: { status: 'partial', target: 'Panel.Icon', special: 'icon' },
704
718
  },
705
719
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bestax-migrate",
3
- "version": "2.27.0",
3
+ "version": "2.29.0",
4
4
  "description": "Codemods for migrating existing React apps from other Bulma libraries to @allxsmith/bestax-bulma",
5
5
  "type": "module",
6
6
  "bin": {
@@ -40,7 +40,7 @@
40
40
  "jscodeshift": "^17.3.0",
41
41
  "parse5": "^7.3.0",
42
42
  "property-information": "^7.2.0",
43
- "@allxsmith/bestax-bulma": "^5.37.0"
43
+ "@allxsmith/bestax-bulma": "^5.39.0"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@jest/globals": "^30.0.0",