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
@@ -22,7 +22,7 @@
22
22
  import { BADGE_PROPS, MAPPING, TOOLTIP_PROPS, UNIVERSAL_PROPS, resolveMapping, } from './mapping.js';
23
23
  import { addTodo, attributesOf, findAttr, jsxNameParts, literalValueOf, removeAttr, renameElement, } from '../_shared/jsx-utils.js';
24
24
  import { applyPropAction, applyUniversalProps } from '../_shared/props.js';
25
- import { collectBoundNames, makeReserve, nameOf, prefersTabs, resolvesToBinding, } from '../_shared/imports.js';
25
+ import { collectBoundNames, makeAliasRegistry, makeReserve, nameOf, prefersTabs, resolvesToBinding, } from '../_shared/imports.js';
26
26
  import { flattenResponsiveProps, RESPONSIVE_KINDS } from './responsive.js';
27
27
  import { runSpecial } from './specials.js';
28
28
  /* eslint-disable @typescript-eslint/no-explicit-any */
@@ -108,6 +108,21 @@ export default function transform(fileInfo, api, options = {}) {
108
108
  addTodo(ctx, path, 'imports', `\`${source}\` reaches into rbx's internals; bestax's equivalents are its \`Theme\` and \`ConfigProvider\` helpers (https://bestax.io/docs/api/helpers/theme)`);
109
109
  }
110
110
  });
111
+ // A barrel re-export reaches the library without an import declaration,
112
+ // so nothing above saw it; it cannot be rewritten (a compound bestax
113
+ // target has no single name to re-export) and must not vanish silently.
114
+ root
115
+ .find(j.ExportNamedDeclaration)
116
+ .filter(p => String(p.node.source?.value ?? '') === RBX)
117
+ .forEach(p => {
118
+ addTodo(ctx, p, 'imports', `re-exports from '${RBX}' were not migrated; import the components, migrate them, then re-export the bestax ones by hand`);
119
+ });
120
+ root
121
+ .find(j.ExportAllDeclaration)
122
+ .filter(p => String(p.node.source?.value ?? '') === RBX)
123
+ .forEach(p => {
124
+ addTodo(ctx, p, 'imports', `\`export * from '${RBX}'\` was not migrated; re-export the bestax components you need by hand`);
125
+ });
111
126
  // ---- 1a. Stylesheet imports (mode-driven) -----------------------------
112
127
  // `bestax` (default): everything converges on the recommended combined
113
128
  // bundle. `bulma`: plain Bulma v1 CSS plus the separate extras file.
@@ -199,68 +214,20 @@ export default function transform(fileInfo, api, options = {}) {
199
214
  // The scope each binding was collected in, so a reference can be checked
200
215
  // against the binding it actually resolves to rather than matched by name.
201
216
  const programScope = root.find(j.Program).paths()[0]?.scope;
202
- // Aliases are keyed by the AST node that owns the declaring scope, not by
203
- // bare name. Two functions can each destructure `Header` from a different
204
- // component; a name-keyed map let the second overwrite the first, and both
205
- // declarations were then pruned while both `<Header…>` references were left
206
- // resolving to nothing.
207
- //
208
- // Resolution walks the reference's ANCESTORS rather than consulting
209
- // `scope.lookup`, because by the time references are rewritten the alias
210
- // pass has already pruned the declaration -- so the binding is gone from
211
- // the scope table, and `path.scope` objects are not identity-stable across
212
- // traversals either.
213
- const aliasesByOwner = new Map();
214
- const ownersFor = (name) => {
215
- const owners = [];
216
- for (const [owner, perScope] of aliasesByOwner) {
217
- if (perScope.has(name))
218
- owners.push(owner);
219
- }
220
- return owners;
221
- };
222
- const aliasAt = (name, at) => {
223
- const owners = ownersFor(name);
224
- if (owners.length === 0)
225
- return undefined;
226
- // Without a reference location there is nothing to walk, so the only
227
- // owner is the best answer available. WITH one, always walk: an unrelated
228
- // `Header` parameter in another function must not resolve to the alias
229
- // merely because the alias is the only one by that name. A single-owner
230
- // shortcut here rewrote such a parameter to `{ Header: Card.Header }`,
231
- // which is not even valid syntax.
232
- if (at === undefined) {
233
- return owners.length === 1
234
- ? aliasesByOwner.get(owners[0]).get(name)
235
- : undefined;
236
- }
237
- let cursor = at;
238
- while (cursor) {
239
- if (cursor.node !== undefined) {
240
- const hit = aliasesByOwner.get(cursor.node)?.get(name);
241
- if (hit) {
242
- // The walk finds the nearest OWNER, but a binding can intervene
243
- // between the reference and that owner: `function F(Header)` under
244
- // a module-level `const { Header } = Card` shadows the alias, and
245
- // resolving through it rewrote the parameter itself to
246
- // `F(Card.Header)`. If the scope table still binds the name
247
- // somewhere other than the owner, that nearer binding wins. (After
248
- // the alias declaration is pruned the lookup returns nothing for
249
- // the alias itself, which is the case this must keep allowing.)
250
- const declaring = at.scope?.lookup(name)?.path?.node;
251
- if (declaring !== undefined &&
252
- declaring !== null &&
253
- declaring !== cursor.node) {
254
- return undefined;
255
- }
256
- return hit;
257
- }
258
- }
259
- cursor = cursor.parent;
260
- }
261
- return undefined;
262
- };
263
- const anyAlias = (name) => ownersFor(name).length > 0;
217
+ const { register: registerAlias, aliasAt, anyAlias } = makeAliasRegistry();
218
+ /**
219
+ * Locals a re-export names. Pruning the declaration that binds one of them
220
+ * (`const { Header } = Card; export { Header };`) leaves the specifier
221
+ * pointing at nothing, so those declarations are kept: the value-reference
222
+ * pass rewrites their initialiser to the bestax component, and the export
223
+ * goes on re-exporting the same member.
224
+ */
225
+ const reExported = new Set();
226
+ root.find(j.ExportSpecifier).forEach(p => {
227
+ const local = p.node.local ?? p.node.exported;
228
+ if (local?.type === 'Identifier')
229
+ reExported.add(local.name);
230
+ });
264
231
  // ---- 1b. Resolve `const { Item } = Card` destructuring -----------------
265
232
  root.find(j.VariableDeclarator).forEach(path => {
266
233
  const node = path.node;
@@ -283,13 +250,8 @@ export default function transform(fileInfo, api, options = {}) {
283
250
  prop.value?.type === 'Identifier') {
284
251
  const owner = path.scope
285
252
  ?.path?.node;
286
- let perScope = aliasesByOwner.get(owner);
287
- if (!perScope) {
288
- perScope = new Map();
289
- aliasesByOwner.set(owner, perScope);
290
- }
291
253
  const target = [...basePath, prop.key.name];
292
- perScope.set(prop.value.name, target);
254
+ registerAlias(owner, prop.value.name, target);
293
255
  aliases.set(prop.value.name, target);
294
256
  // Pruning the declaration is only safe if every use of the alias will
295
257
  // actually be rewritten. For a name the table cannot map, the JSX is
@@ -310,7 +272,10 @@ export default function transform(fileInfo, api, options = {}) {
310
272
  allResolved = false;
311
273
  }
312
274
  }
313
- if (allResolved) {
275
+ const exportsAnAlias = node.id.properties.some((prop) => (prop.type === 'ObjectProperty' || prop.type === 'Property') &&
276
+ prop.value?.type === 'Identifier' &&
277
+ reExported.has(prop.value.name));
278
+ if (allResolved && !exportsAnAlias) {
314
279
  const declaration = path.parent;
315
280
  if (declaration?.node?.type === 'VariableDeclaration' &&
316
281
  declaration.node.declarations.length === 1) {
@@ -426,6 +391,16 @@ export default function transform(fileInfo, api, options = {}) {
426
391
  bound.add(head);
427
392
  });
428
393
  ctx.reserve = makeReserve(ctx, bound);
394
+ // Names the passes actually asked for. `ctx.needed` is also seeded from an
395
+ // existing bestax import's specifiers below (so JSX reuses their locals),
396
+ // and that seeding alone must not turn a type-only specifier into a value
397
+ // import nobody needed.
398
+ const requested = new Set();
399
+ const baseReserve = ctx.reserve;
400
+ ctx.reserve = root => {
401
+ requested.add(root);
402
+ return baseReserve(root);
403
+ };
429
404
  // Merge with an existing bestax import: reuse its locals verbatim.
430
405
  // Only a declaration that already uses NAMED specifiers can absorb more of
431
406
  // them. `import * as Bestax from '…'` cannot: a namespace specifier may not
@@ -434,13 +409,24 @@ export default function transform(fileInfo, api, options = {}) {
434
409
  const existingBestax = root
435
410
  .find(j.ImportDeclaration, { source: { value: BESTAX } })
436
411
  .paths()
437
- .find(p => (p.node.specifiers ?? []).every((spec) => spec.type === 'ImportSpecifier'));
412
+ .find(p =>
413
+ // A type-only declaration cannot take a value specifier: merging a
414
+ // component into `import type { … }` erases it at runtime.
415
+ p.node.importKind !== 'type' &&
416
+ (p.node.specifiers ?? []).every((spec) => spec.type === 'ImportSpecifier'));
438
417
  const preExistingImports = new Set();
439
418
  if (existingBestax) {
440
419
  for (const spec of existingBestax.node.specifiers ?? []) {
441
420
  if (spec.type === 'ImportSpecifier' && spec.local) {
421
+ // The local name is reused either way, so the JSX never needs an
422
+ // alias. But an inline `type` specifier (`import { type Box }`) is
423
+ // not a value binding: it is not counted as already imported, so the
424
+ // component is written as a value below — onto this specifier, which
425
+ // keeps its name, rather than as a duplicate.
442
426
  ctx.needed.set(nameOf(spec.imported), nameOf(spec.local));
443
- preExistingImports.add(nameOf(spec.imported));
427
+ if (spec.importKind !== 'type') {
428
+ preExistingImports.add(nameOf(spec.imported));
429
+ }
444
430
  }
445
431
  }
446
432
  }
@@ -633,6 +619,10 @@ export default function transform(fileInfo, api, options = {}) {
633
619
  parentType === 'JSXMemberExpression') {
634
620
  return;
635
621
  }
622
+ // A TS qualified name (`RBC.ButtonProps` in a type position) is erased at
623
+ // runtime, so it is not a value use of the namespace at all.
624
+ if (parentType === 'TSQualifiedName')
625
+ return;
636
626
  // A namespace binding in a real value position. `rbx.Button` is still a
637
627
  // mappable component reference, so fall through to the member-expression
638
628
  // branch below with an empty prefix (the namespace itself is not part of
@@ -643,6 +633,46 @@ export default function transform(fileInfo, api, options = {}) {
643
633
  parentNode.object === path.node &&
644
634
  !parentNode.computed)) {
645
635
  namespaceStillReferenced = true;
636
+ addTodo(ctx, path, 'value-reference', `\`${name}\` is the library's namespace import used as a plain value (destructured, or passed around); nothing reached through it was migrated — reference its components as \`${name}.Component\` or import them by name, then re-run`);
637
+ return;
638
+ }
639
+ // Members, keys and signatures name a thing; they do not reference the
640
+ // import. An export specifier does, and is handled on its own: a flat
641
+ // target keeps the public name (\`export { SubTitle as Subtitle }\`), a
642
+ // dotted one cannot be re-exported under a member name and is flagged.
643
+ if (parentType === 'ExportSpecifier') {
644
+ if (parentNode.local !== path.node)
645
+ return;
646
+ const exportedName = nameOf(parentNode.exported ?? parentNode.local);
647
+ const mapping = aliasPath ? resolveMapping(aliasPath) : MAPPING[imported];
648
+ if (mapping &&
649
+ mapping.status !== 'todo' &&
650
+ mapping.target &&
651
+ !mapping.target.includes('.')) {
652
+ const local = ctx.reserve(mapping.target);
653
+ // Replace the node: recast reprints a specifier whose identifiers were
654
+ // swapped in place in its original shorthand form, dropping the `as`.
655
+ path.parent.replace(j.exportSpecifier.from({
656
+ local: j.identifier(local),
657
+ exported: j.identifier(exportedName),
658
+ }));
659
+ ctx.dirty = true;
660
+ return;
661
+ }
662
+ ctx.retained.add(imported);
663
+ addTodo(ctx, path, 'value-reference', `\`${name}\` is re-exported; ${mapping?.target ? `its bestax counterpart \`${mapping.target}\` is a member of \`${mapping.target.split('.')[0]}\` and cannot be re-exported under this name` : 'it has no bestax counterpart to re-export'} — migrate the consumers by hand`);
664
+ return;
665
+ }
666
+ if (parentType === 'TSEnumMember' ||
667
+ ((parentType === 'ObjectMethod' ||
668
+ parentType === 'ClassMethod' ||
669
+ parentType === 'ClassProperty' ||
670
+ parentType === 'PropertyDefinition' ||
671
+ parentType === 'MethodDefinition' ||
672
+ parentType === 'TSPropertySignature' ||
673
+ parentType === 'TSMethodSignature') &&
674
+ parentNode.key === path.node &&
675
+ !parentNode.computed)) {
646
676
  return;
647
677
  }
648
678
  // Non-reference positions: member property names and object keys.
@@ -835,15 +865,61 @@ export default function transform(fileInfo, api, options = {}) {
835
865
  if (imports.size > 0) {
836
866
  const retainedNames = [...ctx.retained].sort((a, b) => a.localeCompare(b));
837
867
  const freshNames = [...ctx.needed.entries()]
838
- .filter(([imported]) => !preExistingImports.has(imported))
868
+ .filter(([imported]) => requested.has(imported) && !preExistingImports.has(imported))
839
869
  .sort((a, b) => a[0].localeCompare(b[0]));
840
870
  const bestaxImport = freshNames.length > 0
841
871
  ? j.importDeclaration(freshNames.map(([imported, local]) => j.importSpecifier(j.identifier(imported), j.identifier(local))), j.stringLiteral(BESTAX))
842
872
  : null;
873
+ /**
874
+
875
+ * A pruned declaration takes its comments with it — a licence header, an
876
+
877
+ * eslint directive. Hand them to what replaces it, or to the next statement.
878
+
879
+ */
880
+ const carryComments = (node, importPath) => {
881
+ const comments = node.comments ?? [];
882
+ if (comments.length === 0)
883
+ return;
884
+ const body = importPath.parent?.node?.body ?? [];
885
+ const index = body.indexOf(node);
886
+ // The fresh specifiers are merged INTO an existing bestax import when there
887
+ // is one, and the `bestaxImport` node is then discarded — so the comments
888
+ // have to follow the declaration that survives, not the one that does not.
889
+ const carrier = (existingBestax ? existingBestax.node : bestaxImport) ??
890
+ (index >= 0 ? body[index + 1] : undefined);
891
+ if (carrier) {
892
+ carrier.comments = [...comments, ...(carrier.comments ?? [])];
893
+ }
894
+ else if (index > 0) {
895
+ // The import was the last statement: the comments stay where they
896
+ // were, after what precedes it.
897
+ const previous = body[index - 1];
898
+ previous.comments = [
899
+ ...(previous.comments ?? []),
900
+ ...comments.map((c) => ({
901
+ ...c,
902
+ leading: false,
903
+ trailing: true,
904
+ })),
905
+ ];
906
+ }
907
+ else {
908
+ // The import was the only statement: the comments become the file's.
909
+ const program = importPath.parent?.node;
910
+ if (program)
911
+ program.comments = [...comments, ...(program.comments ?? [])];
912
+ }
913
+ node.comments = [];
914
+ };
843
915
  let inserted = false;
844
916
  // A retained rbx specifier must never collide with a bestax import local
845
917
  // (possible when one component is both JSX-migrated and value-retained).
846
- const bestaxLocals = new Set(ctx.needed.values());
918
+ // Only a VALUE local can collide at runtime: a type-only specifier the
919
+ // seeding above recorded, and nothing promoted, binds no value.
920
+ const bestaxLocals = new Set([...ctx.needed.entries()]
921
+ .filter(([imported]) => requested.has(imported) || preExistingImports.has(imported))
922
+ .map(([, local]) => local));
847
923
  for (const path of rbxImportPaths) {
848
924
  const node = path.node;
849
925
  const keepSpecifiers = (node.specifiers ?? []).filter((spec) => {
@@ -862,10 +938,23 @@ export default function transform(fileInfo, api, options = {}) {
862
938
  });
863
939
  if (!inserted) {
864
940
  if (existingBestax && bestaxImport) {
865
- existingBestax.node.specifiers = [
866
- ...(existingBestax.node.specifiers ?? []),
867
- ...bestaxImport.specifiers,
868
- ];
941
+ const current = existingBestax.node.specifiers ?? [];
942
+ const appended = [];
943
+ for (const fresh of bestaxImport.specifiers) {
944
+ // A type-only specifier for the same name becomes the value
945
+ // import (a value import carries the type too); appending a
946
+ // second `Box` beside `type Box` would be a duplicate identifier.
947
+ const typeOnly = current.find((spec) => spec.type === 'ImportSpecifier' &&
948
+ spec.importKind === 'type' &&
949
+ nameOf(spec.imported) === nameOf(fresh.imported));
950
+ if (typeOnly) {
951
+ typeOnly.importKind = null;
952
+ }
953
+ else {
954
+ appended.push(fresh);
955
+ }
956
+ }
957
+ existingBestax.node.specifiers = [...current, ...appended];
869
958
  }
870
959
  else if (bestaxImport) {
871
960
  path.insertBefore(bestaxImport);
@@ -892,6 +981,7 @@ export default function transform(fileInfo, api, options = {}) {
892
981
  }
893
982
  }
894
983
  else {
984
+ carryComments(node, path);
895
985
  path.prune();
896
986
  }
897
987
  ctx.dirty = true;
@@ -26,7 +26,7 @@ export const UNIVERSAL_PROPS = {
26
26
  todo: 'this bestax component has no `as` prop; restructure the element instead',
27
27
  },
28
28
  domRef: {
29
- todo: 'bestax-bulma components do not take domRef; use a ref on a DOM child or wrap the component',
29
+ todo: 'bestax-bulma has no `domRef`. The form controls and `Button`, `LinkButton`, `Modal`, `Dropdown`, `Navbar` (plus `Navbar.Burger` and `Navbar.Link`), `Dialog`, `Sidebar`, `Toast` and `Carousel` forward a ref — on those, rename `domRef` to `ref`. Mind the `Navbar.Dropdown` collision: the `Navbar.Item` that wrapped your dropdown became bestax `Navbar.Dropdown` and forwards one, while your `Navbar.Dropdown` became `Navbar.DropdownMenu` and does not. The rest forward no ref: put the ref on a DOM child or wrap the component',
30
30
  },
31
31
  // ColorProps
32
32
  backgroundColor: { rename: 'bgColor' },
@@ -555,12 +555,12 @@ export const MAPPING = {
555
555
  props: {
556
556
  show: { rename: 'active' },
557
557
  onClose: {},
558
- closeOnEsc: { todo: 'bestax Modal closes on Esc by default; remove' },
558
+ closeOnEsc: { rename: 'closeOnEscape' },
559
559
  closeOnBlur: {
560
- todo: 'bestax Modal background click closes when onClose is set; remove',
560
+ todo: 'bestax Modal closes on background click only in its legacy form; the Modal.Content/Modal.Card child selects the compound form, which renders only the children you wrote — add a `<Modal.Background>` whose `onClick` calls your close handler, or drop the prop if you passed false',
561
561
  },
562
562
  showClose: {
563
- todo: 'bestax Modal renders the close button when onClose is set; remove',
563
+ todo: 'bestax Modal renders a close button only in its legacy form; the Modal.Content/Modal.Card child selects the compound form, which renders only the children you wrote — add a `<Modal.Close variant="floating">` whose `onClick` calls your close handler, or drop the prop if you passed false',
564
564
  },
565
565
  document: { drop: true },
566
566
  },
@@ -1 +1 @@
1
- {"version":3,"file":"responsive.d.ts","sourceRoot":"","sources":["../../../src/sources/react-bulma-components/responsive.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;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,SAAS,GAAG,QAAQ,CAAC;AAkB9D,wBAAgB,sBAAsB,CACpC,GAAG,EAAE,gBAAgB,EACrB,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,EAClB,OAAO,EAAE,GAAG,EACZ,IAAI,EAAE,cAAc,GACnB,IAAI,CAkHN"}
1
+ {"version":3,"file":"responsive.d.ts","sourceRoot":"","sources":["../../../src/sources/react-bulma-components/responsive.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,EAOL,KAAK,gBAAgB,EACtB,MAAM,yBAAyB,CAAC;AAIjC,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAC;AAS9D,wBAAgB,sBAAsB,CACpC,GAAG,EAAE,gBAAgB,EACrB,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,EAClB,OAAO,EAAE,GAAG,EACZ,IAAI,EAAE,cAAc,GACnB,IAAI,CAkHN"}
@@ -11,25 +11,13 @@
11
11
  * TODO. Non-object or dynamic values get a TODO.
12
12
  */
13
13
  import { RESPONSIVE_BREAKPOINTS } from './mapping.js';
14
- import { addAttr, addTodo, attributesOf, makeAttr, removeAttr, } from '../_shared/jsx-utils.js';
14
+ import { addAttr, addTodo, attributesOf, literalOf, makeAttr, removeAttr, } from '../_shared/jsx-utils.js';
15
15
  const TEXT_ALIGN_MAP = {
16
16
  center: 'centered',
17
17
  justify: 'justified',
18
18
  left: 'left',
19
19
  right: 'right',
20
20
  };
21
- function literalOf(prop) {
22
- const v = prop.value;
23
- if (!v)
24
- return undefined;
25
- if (v.type === 'StringLiteral')
26
- return v.value;
27
- if (v.type === 'NumericLiteral')
28
- return v.value;
29
- if (v.type === 'BooleanLiteral')
30
- return v.value;
31
- return undefined;
32
- }
33
21
  export function flattenResponsiveProps(ctx, path, element, kind) {
34
22
  const { j } = ctx;
35
23
  for (const attr of [...attributesOf(element)]) {
@@ -57,7 +45,7 @@ export function flattenResponsiveProps(ctx, path, element, kind) {
57
45
  continue;
58
46
  }
59
47
  const key = prop.key?.name ?? prop.key?.value;
60
- const literal = literalOf(prop);
48
+ const literal = literalOf(prop.value);
61
49
  if (key === 'display' && typeof literal === 'string') {
62
50
  if (literal === 'hidden') {
63
51
  addAttr(element, makeAttr(j, `visibility${suffix}`, 'hidden'));
@@ -1 +1 @@
1
- {"version":3,"file":"specials.d.ts","sourceRoot":"","sources":["../../../src/sources/react-bulma-components/specials.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,EAWL,KAAK,gBAAgB,EACtB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAML,KAAK,aAAa,EACnB,MAAM,8BAA8B,CAAC;AAEtC,YAAY,EAAE,aAAa,EAAE,CAAC;AAsvB9B,wBAAgB,UAAU,CACxB,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,gBAAgB,EACrB,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,EAClB,OAAO,EAAE,GAAG,GACX,aAAa,CAIf;AAED,kEAAkE;AAClE,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,GAAG,QAAQ,CAGjE,CAAC"}
1
+ {"version":3,"file":"specials.d.ts","sourceRoot":"","sources":["../../../src/sources/react-bulma-components/specials.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,EAWL,KAAK,gBAAgB,EACtB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAOL,KAAK,aAAa,EACnB,MAAM,8BAA8B,CAAC;AAEtC,YAAY,EAAE,aAAa,EAAE,CAAC;AAgxB9B,wBAAgB,UAAU,CACxB,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,gBAAgB,EACrB,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,EAClB,OAAO,EAAE,GAAG,GACX,aAAa,CAIf;AAED,kEAAkE;AAClE,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,GAAG,QAAQ,CAGjE,CAAC"}
@@ -9,8 +9,19 @@
9
9
  */
10
10
  import { RESPONSIVE_BREAKPOINTS, UNIVERSAL_PROPS } from './mapping.js';
11
11
  import { addAttr, addTodo, attributesOf, buildJsxName, findAttr, literalValueOf, makeAttr, plainElement, removeAttr, resolveBooleanish, } from '../_shared/jsx-utils.js';
12
- import { alignTarget, makeStripModifierProps, mergeClassName, parseIconClasses, } from '../_shared/specials-utils.js';
12
+ import { alignTarget, makeStripModifierProps, mergeClassName, applyIconProps, parseIconClasses, } from '../_shared/specials-utils.js';
13
13
  /* eslint-disable @typescript-eslint/no-explicit-any */
14
+ /**
15
+ * The attributes a plain-element rewrite keeps: the modifier strip decides
16
+ * which NAMED props survive, and the element's spreads ride along in their
17
+ * original places — they are the caller's own props, which a plain element
18
+ * takes as readily as the component did. `attributesOf` filters spreads out,
19
+ * so passing its result alone dropped `{...rest}` with no TODO.
20
+ */
21
+ function keptAttrs(ctx, path, element, where) {
22
+ const kept = new Set(stripModifierProps(ctx, path, attributesOf(element), where));
23
+ return (element.openingElement.attributes ?? []).filter((a) => a.type === 'JSXSpreadAttribute' || kept.has(a));
24
+ }
14
25
  const stripModifierProps = makeStripModifierProps(UNIVERSAL_PROPS, RESPONSIVE_BREAKPOINTS);
15
26
  const SPECIALS = {
16
27
  /**
@@ -61,7 +72,18 @@ const SPECIALS = {
61
72
  // (`remove={false}`, `remove=""`, `remove={0}`) → just drop the prop.
62
73
  removeAttr(element, attr);
63
74
  ctx.dirty = true;
64
- return resolved === 'truthy' ? { target: 'Delete' } : {};
75
+ if (resolved !== 'truthy')
76
+ return {};
77
+ // The universal `domRef` advice lists `Button` among the components that
78
+ // forward a ref, but this element is no longer a Button: `Delete` is a
79
+ // plain function component. Claim the prop so that advice cannot fire
80
+ // here, and say what actually applies to the target we picked.
81
+ const handledProps = [];
82
+ if (findAttr(element, 'domRef')) {
83
+ addTodo(ctx, path, 'prop:domRef', 'this `<Button remove>` became bestax `<Delete>`, which forwards no ref — the general `domRef` advice does not apply here; put the ref on a DOM child or wrap the component');
84
+ handledProps.push('domRef');
85
+ }
86
+ return { target: 'Delete', handledProps };
65
87
  }
66
88
  addTodo(ctx, path, 'prop:remove', '`remove` has a dynamic value; this can render as either a Button or a Delete cross — split the branch by hand');
67
89
  return {};
@@ -113,7 +135,8 @@ const SPECIALS = {
113
135
  if (headingTruthy && !subtitleDynamic) {
114
136
  removeAttr(element, headingAttr);
115
137
  const className = mergeClassName(ctx, path, element, subtitleTruthy ? 'heading subtitle' : 'heading', 'Heading');
116
- const rest = stripModifierProps(ctx, path, attributesOf(element).filter(a => !['size', 'weight', 'spaced', 'subtitle'].includes(a.name.name)), 'Heading');
138
+ const consumed = new Set(stripModifierProps(ctx, path, attributesOf(element).filter(a => !['size', 'weight', 'spaced', 'subtitle'].includes(a.name.name)), 'Heading'));
139
+ const rest = (element.openingElement.attributes ?? []).filter((a) => a.type === 'JSXSpreadAttribute' || consumed.has(a));
117
140
  const replacement = plainElement(ctx.j, 'p', className, rest, element.children ?? []);
118
141
  path.replace(replacement);
119
142
  ctx.dirty = true;
@@ -296,7 +319,7 @@ const SPECIALS = {
296
319
  }
297
320
  }
298
321
  className = mergeClassName(ctx, path, element, className, 'Form.Label');
299
- const rest = stripModifierProps(ctx, path, attributesOf(element), 'Form.Label');
322
+ const rest = keptAttrs(ctx, path, element, 'Form.Label');
300
323
  path.replace(plainElement(ctx.j, 'label', className, rest, element.children ?? []));
301
324
  ctx.dirty = true;
302
325
  return { replaced: true };
@@ -317,7 +340,7 @@ const SPECIALS = {
317
340
  }
318
341
  }
319
342
  className = mergeClassName(ctx, path, element, className, 'Form.Help');
320
- const rest = stripModifierProps(ctx, path, attributesOf(element), 'Form.Help');
343
+ const rest = keptAttrs(ctx, path, element, 'Form.Help');
321
344
  path.replace(plainElement(ctx.j, 'p', className, rest, element.children ?? []));
322
345
  ctx.dirty = true;
323
346
  return { replaced: true };
@@ -347,7 +370,9 @@ const SPECIALS = {
347
370
  const value = cls && cls.value && cls.value.type === 'StringLiteral'
348
371
  ? cls.value.value
349
372
  : null;
350
- return value ? parseIconClasses(value) : null;
373
+ const read = value ? parseIconClasses(value) : null;
374
+ // An app's own class on the <i> has no home on bestax's glyph.
375
+ return read && read.leftovers.length === 0 ? read : null;
351
376
  })()
352
377
  : null;
353
378
  if (!parsed) {
@@ -355,11 +380,8 @@ const SPECIALS = {
355
380
  return {};
356
381
  }
357
382
  if (!findAttr(element, 'name') && !findAttr(element, 'icon')) {
358
- addAttr(element, makeAttr(ctx.j, 'name', parsed.name));
359
- if (parsed.library)
360
- addAttr(element, makeAttr(ctx.j, 'library', parsed.library));
361
- if (parsed.variant)
362
- addAttr(element, makeAttr(ctx.j, 'variant', parsed.variant));
383
+ applyIconProps(ctx, path, element, parsed);
384
+ return {};
363
385
  }
364
386
  element.children = [];
365
387
  if (element.closingElement) {
@@ -372,7 +394,7 @@ const SPECIALS = {
372
394
  /** RBC Loader is a plain <div class="loader"> — keep exactly that. */
373
395
  'plain-loader'(ctx, path, element) {
374
396
  const className = mergeClassName(ctx, path, element, 'loader', 'Loader');
375
- const rest = stripModifierProps(ctx, path, attributesOf(element), 'Loader');
397
+ const rest = keptAttrs(ctx, path, element, 'Loader');
376
398
  path.replace(plainElement(ctx.j, 'div', className, rest, element.children ?? []));
377
399
  ctx.dirty = true;
378
400
  return { replaced: true };
@@ -419,7 +441,7 @@ const SPECIALS = {
419
441
  removeAttr(element, activeAttr);
420
442
  }
421
443
  className = mergeClassName(ctx, path, element, className, 'Panel.Tabs.Tab');
422
- const rest = stripModifierProps(ctx, path, attributesOf(element), 'Panel.Tabs.Tab');
444
+ const rest = keptAttrs(ctx, path, element, 'Panel.Tabs.Tab');
423
445
  path.replace(plainElement(ctx.j, 'a', className, rest, element.children ?? []));
424
446
  ctx.dirty = true;
425
447
  return { replaced: true };
@@ -439,7 +461,7 @@ const SPECIALS = {
439
461
  removeAttr(element, activeAttr);
440
462
  }
441
463
  liClass = mergeClassName(ctx, path, element, liClass, 'Breadcrumb.Item');
442
- const anchorAttrs = stripModifierProps(ctx, path, attributesOf(element), 'Breadcrumb.Item');
464
+ const anchorAttrs = keptAttrs(ctx, path, element, 'Breadcrumb.Item');
443
465
  const children = element.children ?? [];
444
466
  const solidChildren = children.filter((c) => !(c.type === 'JSXText' && c.value.trim() === ''));
445
467
  // `<Breadcrumb.Item><a href>…</a></Breadcrumb.Item>` already carries its
@@ -475,7 +497,7 @@ const SPECIALS = {
475
497
  return { replaced: true };
476
498
  }
477
499
  const className = mergeClassName(ctx, path, element, 'table-container', 'Table.Container');
478
- const rest = stripModifierProps(ctx, path, attributesOf(element), 'Table.Container');
500
+ const rest = keptAttrs(ctx, path, element, 'Table.Container');
479
501
  path.replace(plainElement(ctx.j, 'div', className, rest, element.children ?? []));
480
502
  ctx.dirty = true;
481
503
  return { replaced: true };
@@ -496,9 +518,14 @@ const SPECIALS = {
496
518
  solid[0].openingElement.name.name === 'a';
497
519
  if (alreadyAnchor)
498
520
  return {};
499
- const anchorAttrs = stripModifierProps(ctx, path, attributesOf(element).filter(a => !['active', 'className'].includes(a.name.name)), 'Tabs.Tab');
500
- for (const attr of attributesOf(element)) {
501
- if (!['active', 'className'].includes(attr.name.name)) {
521
+ const anchorKept = new Set(stripModifierProps(ctx, path, attributesOf(element).filter(a => !['active', 'className'].includes(a.name.name)), 'Tabs.Tab'));
522
+ // The anchor takes everything that is not the tab's own state, spreads
523
+ // included: they were the caller's props on the element that becomes the
524
+ // <a>, and `attributesOf` filters them out.
525
+ const anchorAttrs = (element.openingElement.attributes ?? []).filter((a) => a.type === 'JSXSpreadAttribute' || anchorKept.has(a));
526
+ for (const attr of [...(element.openingElement.attributes ?? [])]) {
527
+ if (attr.type === 'JSXSpreadAttribute' ||
528
+ !['active', 'className'].includes(attr.name.name)) {
502
529
  removeAttr(element, attr);
503
530
  }
504
531
  }
@@ -1 +1 @@
1
- {"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../../../src/sources/react-bulma-components/transform.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AA2CvD,MAAM,CAAC,OAAO,UAAU,SAAS,CAC/B,QAAQ,EAAE,QAAQ,EAClB,GAAG,EAAE,GAAG,EACR,OAAO,GAAE,gBAAqB,GAC7B,MAAM,GAAG,SAAS,CAyzBpB"}
1
+ {"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../../../src/sources/react-bulma-components/transform.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AA4CvD,MAAM,CAAC,OAAO,UAAU,SAAS,CAC/B,QAAQ,EAAE,QAAQ,EAClB,GAAG,EAAE,GAAG,EACR,OAAO,GAAE,gBAAqB,GAC7B,MAAM,GAAG,SAAS,CAq8BpB"}