bestax-migrate 2.4.0 → 2.6.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.
@@ -8,9 +8,9 @@
8
8
  * it: each root, modifier and helper here is rendered through the built
9
9
  * library and compared with the raw markup it replaces.
10
10
  *
11
- * Data only, and written so a generator in another package can import it
12
- * directly: nothing Node cannot strip, so no enums, no namespaces, type
13
- * imports only.
11
+ * Data only: `scripts/gen-mcp-index.mjs` imports it directly for the MCP
12
+ * server's `lookup_bulma_classes`, so nothing Node cannot strip (no enums, no
13
+ * namespaces, type imports only), and a change to it wants `pnpm gen:mcp`.
14
14
  */
15
15
  export interface PropWrite {
16
16
  readonly prop: string;
@@ -37,7 +37,10 @@ export interface Modifier {
37
37
  export type RootStatus = 'mapped' | 'todo' | 'plain';
38
38
  export interface RootEntry {
39
39
  readonly status: RootStatus;
40
- /** bestax JSX name, dotted for a part (`Hero.Body`). */
40
+ /**
41
+ * bestax JSX name, dotted for a part (`Hero.Body`). On a `todo` family, the
42
+ * component a person converts the family to by hand.
43
+ */
41
44
  readonly target?: string;
42
45
  /** The tag the target renders with no `as`. */
43
46
  readonly tag?: string;
@@ -60,6 +63,24 @@ export interface RootEntry {
60
63
  readonly untypedAttrs?: readonly string[];
61
64
  /** The target's props type requires children. */
62
65
  readonly requiresChildren?: boolean;
66
+ /**
67
+ * The target renders its children inside an element of its own (`Card`
68
+ * inside `.card-content`) unless one of them is one of these parts, so the
69
+ * element converts only beside a direct child that converts to one.
70
+ * `whenEmpty`: it renders that element with no children too.
71
+ */
72
+ readonly wrapsChildren?: {
73
+ readonly in: string;
74
+ readonly unless: readonly string[];
75
+ readonly whenEmpty?: boolean;
76
+ };
77
+ /** The target takes no helper props, so every helper class stays a class. */
78
+ readonly noHelpers?: boolean;
79
+ /**
80
+ * The target drops its own class when it is given a `className`, so the
81
+ * element converts only when it carries no other class.
82
+ */
83
+ readonly ownClassOnly?: boolean;
63
84
  /**
64
85
  * Attributes the target types as numbers. A numeric string (`value="40"`)
65
86
  * becomes a number, which renders the same; any other string refuses.
@@ -78,6 +99,13 @@ export interface RootEntry {
78
99
  readonly part?: boolean;
79
100
  /** Why a `todo` or `plain` root is what it is; `todo` messages quote it. */
80
101
  readonly why?: string;
102
+ /**
103
+ * Modifiers left out of `modifiers` on purpose, because the prop that
104
+ * renders the class renders more than the class. They stay in `className`;
105
+ * the reason is for the MCP lookup, which would otherwise say bestax has
106
+ * no prop for them.
107
+ */
108
+ readonly omits?: Readonly<Record<string, string>>;
81
109
  }
82
110
  export declare const COLORS: readonly ["primary", "link", "info", "success", "warning", "danger", "black", "black-bis", "black-ter", "grey-darker", "grey-dark", "grey", "grey-light", "grey-lighter", "white", "white-bis", "white-ter", "light", "dark"];
83
111
  export declare const SIZES: readonly ["0", "1", "2", "3", "4", "5", "6", "auto"];
@@ -1 +1 @@
1
- {"version":3,"file":"class-map.d.ts","sourceRoot":"","sources":["../../../src/sources/bulma-classes/class-map.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"class-map.d.ts","sourceRoot":"","sources":["../../../src/sources/bulma-classes/class-map.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,uCAAuC;IACvC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,MAAM,MAAM,GAChB;IAAE,QAAQ,CAAC,EAAE,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,GAAG;IAAE,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,CAAC;AAE9E,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAE1D;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,MAAM,EAAE,SAAS,SAAS,EAAE,CAAC;IACtC,6EAA6E;IAC7E,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACpC;AAED;;;;;GAKG;AACH,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;AAErD,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,+CAA+C;IAC/C,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,6DAA6D;IAC7D,QAAQ,CAAC,EAAE,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,KAAK,CAAC;IACxC,+EAA+E;IAC/E,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,SAAS,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;IACxD,6EAA6E;IAC7E,QAAQ,CAAC,SAAS,CAAC,EAAE,WAAW,GAAG,OAAO,GAAG,IAAI,CAAC;IAClD,mFAAmF;IACnF,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,GAAG,iBAAiB,GAAG,IAAI,CAAC;IACxD,wEAAwE;IACxE,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACrD;;;;OAIG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1C,iDAAiD;IACjD,QAAQ,CAAC,gBAAgB,CAAC,EAAE,OAAO,CAAC;IACpC;;;;;OAKG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE;QACvB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;QACpB,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;QACnC,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;KAC9B,CAAC;IACF,6EAA6E;IAC7E,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAC7B;;;OAGG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,CAAC;IAChC;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC,+EAA+E;IAC/E,QAAQ,CAAC,SAAS,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACtD;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC,oEAAoE;IACpE,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IACxB,4EAA4E;IAC5E,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;OAKG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACnD;AAID,eAAO,MAAM,MAAM,+NAoBT,CAAC;AACX,eAAO,MAAM,KAAK,sDAAuD,CAAC;AAC1E,eAAO,MAAM,UAAU,8CAA+C,CAAC;AACvE,eAAO,MAAM,UAAU,qDAAsD,CAAC;AAC9E,eAAO,MAAM,eAAe,8DAKlB,CAAC;AACX,eAAO,MAAM,YAAY,4DAMf,CAAC;AACX,eAAO,MAAM,aAAa,sEAMhB,CAAC;AACX,eAAO,MAAM,QAAQ,6EAOX,CAAC;AACX,eAAO,MAAM,eAAe,6DAKlB,CAAC;AACX,eAAO,MAAM,UAAU,6CAA8C,CAAC;AACtE,eAAO,MAAM,gBAAgB,iIAWnB,CAAC;AACX,eAAO,MAAM,cAAc,2GAQjB,CAAC;AACX,eAAO,MAAM,WAAW,sFAQd,CAAC;AACX,eAAO,MAAM,WAAW,8EAOd,CAAC;AACX,eAAO,MAAM,gBAAgB,yCAA0C,CAAC;AACxE,eAAO,MAAM,SAAS,6HAUZ,CAAC;AAoDX,kFAAkF;AAClF,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAKvD;AAuDD,eAAO,MAAM,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAi2BrD,CAAC;AAcF;;;;GAIG;AACH,eAAO,MAAM,UAAU,EAAE,SAAS,MAAM,EAgDvC,CAAC;AAEF,yDAAyD;AACzD,eAAO,MAAM,YAAY,EAAE,SAAS,MAAM,EAKzC,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAYrD,CAAC;AAEF,yEAAyE;AACzE,eAAO,MAAM,iBAAiB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAYzE,CAAC;AAEF,6DAA6D;AAC7D,eAAO,MAAM,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAEtD,CAAC;AAIF;;;;GAIG;AACH,MAAM,MAAM,WAAW,GACnB,YAAY,GACZ,YAAY,GACZ,SAAS,GACT,YAAY,GACZ,SAAS,GACT,YAAY,GACZ,gBAAgB,GAChB,WAAW,GACX,OAAO,CAAC;AAEZ,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;CAC3B;AA6DD,eAAO,MAAM,aAAa,EAAE,WAAW,CAAC,MAAM,EAAE,WAAW,CAsGzD,CAAC;AAEH,iEAAiE;AACjE,eAAO,MAAM,YAAY,EAAE,WAAW,CAAC,MAAM,CAS3C,CAAC;AAIH;;;;;GAKG;AACH,eAAO,MAAM,WAAW,EAAE,aAAa,CAAC;IACtC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB,CA8BA,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAEnE;AAiBD,qCAAqC;AACrC,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,CAE5D;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,CAGlE;AAED,qCAAqC;AACrC,wBAAgB,WAAW,CACzB,KAAK,EAAE,SAAS,EAChB,KAAK,EAAE,MAAM,GACZ,QAAQ,GAAG,SAAS,CAEtB;AAED,kDAAkD;AAClD,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAE5D;AAED,0CAA0C;AAC1C,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAE1D"}
@@ -8,9 +8,9 @@
8
8
  * it: each root, modifier and helper here is rendered through the built
9
9
  * library and compared with the raw markup it replaces.
10
10
  *
11
- * Data only, and written so a generator in another package can import it
12
- * directly: nothing Node cannot strip, so no enums, no namespaces, type
13
- * imports only.
11
+ * Data only: `scripts/gen-mcp-index.mjs` imports it directly for the MCP
12
+ * server's `lookup_bulma_classes`, so nothing Node cannot strip (no enums, no
13
+ * namespaces, type imports only), and a change to it wants `pnpm gen:mcp`.
14
14
  */
15
15
  export function inTagSet(set, tag) {
16
16
  return 'on' in set ? set.on.includes(tag) : !set.except.includes(tag);
@@ -246,7 +246,9 @@ export const ROOTS = {
246
246
  'is-active': 'isActive',
247
247
  'is-hovered': 'isHovered',
248
248
  }),
249
- // No `is-disabled`: `isDisabled` writes attributes as well as the class.
249
+ },
250
+ omits: {
251
+ 'is-disabled': 'bestax `Button` renders it through `isDisabled`, which also disables the element (`disabled`, or `aria-disabled` on a link)',
250
252
  },
251
253
  dropsAttr: {
252
254
  // Kept only on the elements `disabled` means something on.
@@ -318,6 +320,86 @@ export const ROOTS = {
318
320
  'size',
319
321
  ],
320
322
  },
323
+ // `Card` checks its children for a part (`hasCompoundComponents` in
324
+ // bulma-ui's Card.tsx), and `Card.Header` for a `Card.Header.Title`
325
+ // (`hasHeaderTitle`), and wraps them when there is none.
326
+ card: {
327
+ ...BASE,
328
+ target: 'Card',
329
+ tag: 'div',
330
+ wrapsChildren: {
331
+ in: 'card-content',
332
+ unless: [
333
+ 'Card.Header',
334
+ 'Card.Header.Icon',
335
+ 'Card.Image',
336
+ 'Card.Content',
337
+ 'Card.Footer',
338
+ 'Card.FooterItem',
339
+ ],
340
+ },
341
+ ownProps: [
342
+ 'textColor',
343
+ 'color',
344
+ 'bgColor',
345
+ 'hasShadow',
346
+ 'header',
347
+ 'headerCentered',
348
+ 'headerIcon',
349
+ 'footer',
350
+ 'image',
351
+ 'imageAlt',
352
+ ],
353
+ },
354
+ 'card-header': {
355
+ ...BASE,
356
+ target: 'Card.Header',
357
+ tag: 'header',
358
+ wrapsChildren: {
359
+ in: 'card-header-title',
360
+ unless: ['Card.Header.Title'],
361
+ whenEmpty: true,
362
+ },
363
+ ownProps: ['color', 'bgColor', 'textColor', 'centered'],
364
+ },
365
+ 'card-header-title': {
366
+ ...BASE,
367
+ target: 'Card.Header.Title',
368
+ tag: 'div',
369
+ modifiers: flags({ 'is-centered': 'centered' }),
370
+ ownProps: ['color', 'bgColor', 'textColor', 'centered'],
371
+ },
372
+ 'card-header-icon': {
373
+ ...BASE,
374
+ target: 'Card.Header.Icon',
375
+ tag: 'button',
376
+ defaults: { 'aria-label': 'more options' },
377
+ ownProps: ['color', 'bgColor', 'textColor'],
378
+ },
379
+ 'card-image': {
380
+ ...BASE,
381
+ target: 'Card.Image',
382
+ tag: 'div',
383
+ ownProps: ['color', 'bgColor', 'textColor'],
384
+ },
385
+ 'card-content': {
386
+ ...BASE,
387
+ target: 'Card.Content',
388
+ tag: 'div',
389
+ ownProps: ['color', 'bgColor', 'textColor'],
390
+ },
391
+ 'card-footer': {
392
+ ...BASE,
393
+ target: 'Card.Footer',
394
+ tag: 'footer',
395
+ ownProps: ['color', 'bgColor', 'textColor'],
396
+ },
397
+ 'card-footer-item': {
398
+ ...BASE,
399
+ target: 'Card.FooterItem',
400
+ tag: 'span',
401
+ ownProps: ['color', 'bgColor', 'textColor'],
402
+ },
321
403
  columns: {
322
404
  ...BASE,
323
405
  target: 'Columns',
@@ -568,6 +650,86 @@ export const ROOTS = {
568
650
  tag: 'div',
569
651
  ownProps: ['color', 'bgColor', 'textColor'],
570
652
  },
653
+ // `Navbar` writes Bulma's own `role` and `aria-label`, so an element that
654
+ // already carries them renders the same.
655
+ navbar: {
656
+ ...BASE,
657
+ target: 'Navbar',
658
+ tag: 'nav',
659
+ modifiers: {
660
+ ...tokens('is-', COMPONENT_COLORS, 'color'),
661
+ ...tokens('is-fixed-', ['top', 'bottom'], 'fixed'),
662
+ ...flags({ 'is-transparent': 'transparent' }),
663
+ },
664
+ defaults: { role: 'navigation', 'aria-label': 'main navigation' },
665
+ ownProps: ['textColor', 'color', 'bgColor', 'transparent', 'fixed'],
666
+ },
667
+ // No background prop on these parts. Menu, Start and End spread `textColor`
668
+ // onto the element rather than reading it, and type `color` to six colors,
669
+ // so their text color stays a class.
670
+ 'navbar-brand': {
671
+ ...BASE,
672
+ target: 'Navbar.Brand',
673
+ tag: 'div',
674
+ bgColor: null,
675
+ ownProps: ['textColor', 'color'],
676
+ },
677
+ 'navbar-menu': {
678
+ ...BASE,
679
+ target: 'Navbar.Menu',
680
+ tag: 'div',
681
+ textColor: null,
682
+ bgColor: null,
683
+ modifiers: flags({ 'is-active': 'active' }),
684
+ ownProps: ['textColor', 'color', 'active'],
685
+ },
686
+ 'navbar-start': {
687
+ ...BASE,
688
+ target: 'Navbar.Start',
689
+ tag: 'div',
690
+ textColor: null,
691
+ bgColor: null,
692
+ ownProps: ['textColor', 'color'],
693
+ },
694
+ 'navbar-end': {
695
+ ...BASE,
696
+ target: 'Navbar.End',
697
+ tag: 'div',
698
+ textColor: null,
699
+ bgColor: null,
700
+ ownProps: ['textColor', 'color'],
701
+ },
702
+ // A `.has-dropdown` item stays a `Navbar.Item` with the class: a
703
+ // `Navbar.Dropdown` would hand its `.navbar-link` dropdown semantics.
704
+ 'navbar-item': {
705
+ ...BASE,
706
+ target: 'Navbar.Item',
707
+ tag: 'a',
708
+ as: 'any',
709
+ modifiers: flags({ 'is-active': 'active' }),
710
+ ownProps: ['as', 'active', 'textColor', 'bgColor'],
711
+ },
712
+ 'navbar-dropdown': {
713
+ status: 'mapped',
714
+ target: 'Navbar.DropdownMenu',
715
+ tag: 'div',
716
+ textColor: null,
717
+ bgColor: null,
718
+ noHelpers: true,
719
+ modifiers: flags({ 'is-right': 'right', 'is-up': 'up' }),
720
+ ownProps: ['right', 'up'],
721
+ },
722
+ 'navbar-divider': {
723
+ status: 'mapped',
724
+ target: 'Navbar.Divider',
725
+ tag: 'hr',
726
+ textColor: null,
727
+ bgColor: null,
728
+ noHelpers: true,
729
+ // bulma-ui's Navbar.Divider spreads `className` over its own class.
730
+ ownClassOnly: true,
731
+ ownProps: [],
732
+ },
571
733
  notification: {
572
734
  ...BASE,
573
735
  target: 'Notification',
@@ -656,7 +818,9 @@ export const ROOTS = {
656
818
  'is-rounded': 'isRounded',
657
819
  'is-hoverable': 'isHoverable',
658
820
  }),
659
- // No `is-delete`: `isDelete` turns the tag into a <button>.
821
+ },
822
+ omits: {
823
+ 'is-delete': 'bestax `Tag` renders it through `isDelete`, which also turns the tag into a <button>',
660
824
  },
661
825
  ownProps: [
662
826
  'color',
@@ -698,51 +862,43 @@ export const ROOTS = {
698
862
  ownProps: ['size', 'isSpaced', 'as', 'hasSkeleton', 'textColor', 'bgColor'],
699
863
  },
700
864
  // ---- Families bestax has but that do not map element by element yet ------
701
- breadcrumb: todo('bestax `Breadcrumb` renders the `<ul>` itself and adds an `aria-label`'),
702
- card: todo('bestax `Card` wraps any child that is not one of its parts in `.card-content`, so convert the card and its parts together, by hand'),
703
- 'card-header': part(),
704
- 'card-header-title': part(),
705
- 'card-header-icon': part(),
706
- 'card-image': part(),
707
- 'card-content': part(),
708
- 'card-footer': part(),
709
- 'card-footer-item': part(),
710
- cell: todo('this source leaves Grid markup as written; convert it to bestax `Grid` and `Cell` by hand (`Grid isFixed` renders the `.fixed-grid` wrapper itself)'),
711
- grid: todo('this source leaves Grid markup as written; convert it to bestax `Grid` and `Cell` by hand (`Grid isFixed` renders the `.fixed-grid` wrapper itself)'),
712
- 'fixed-grid': todo('this source leaves Grid markup as written; convert it to bestax `Grid` and `Cell` by hand (`Grid isFixed` renders the `.fixed-grid` wrapper itself)'),
713
- checkbox: todo("bestax `Checkbox` renders its own styled markup, not Bulma's"),
714
- checkboxes: todo('bestax `Checkboxes` renders its own `.field` and `.control` wrappers'),
715
- radio: todo("bestax `Radio` renders its own styled markup, not Bulma's"),
716
- radios: todo('bestax `Radios` renders its own `.field` and `.control` wrappers'),
717
- field: todo('bestax form controls render their own `.field` and `.control` wrappers; see the unmappables reference'),
865
+ breadcrumb: todo('Breadcrumb', 'bestax `Breadcrumb` renders the `<ul>` itself and adds an `aria-label`'),
866
+ cell: todo('Cell', 'this source leaves Grid markup as written; convert it to bestax `Grid` and `Cell` by hand (`Grid isFixed` renders the `.fixed-grid` wrapper itself)'),
867
+ grid: todo('Grid', 'this source leaves Grid markup as written; convert it to bestax `Grid` and `Cell` by hand (`Grid isFixed` renders the `.fixed-grid` wrapper itself)'),
868
+ 'fixed-grid': todo('Grid', 'this source leaves Grid markup as written; convert it to bestax `Grid` and `Cell` by hand (`Grid isFixed` renders the `.fixed-grid` wrapper itself)'),
869
+ checkbox: todo('Checkbox', "bestax `Checkbox` renders its own styled markup, not Bulma's"),
870
+ checkboxes: todo('Checkboxes', 'bestax `Checkboxes` renders its own `.field` and `.control` wrappers'),
871
+ radio: todo('Radio', "bestax `Radio` renders its own styled markup, not Bulma's"),
872
+ radios: todo('Radios', 'bestax `Radios` renders its own `.field` and `.control` wrappers'),
873
+ field: todo('Field', 'bestax form controls render their own `.field` and `.control` wrappers; see the unmappables reference'),
718
874
  'field-label': part(),
719
875
  'field-body': part(),
720
876
  control: part(),
721
- input: todo('bestax `Input` renders its own `.field` and `.control` wrappers'),
722
- textarea: todo('bestax `TextArea` renders its own `.field` and `.control` wrappers'),
723
- select: todo('bestax `Select` renders the `.select` wrapper and the `<select>` together'),
724
- file: todo('bestax `File` renders the whole `.file-label` tree itself'),
877
+ input: todo('Input', 'bestax `Input` renders its own `.field` and `.control` wrappers'),
878
+ textarea: todo('TextArea', 'bestax `TextArea` renders its own `.field` and `.control` wrappers'),
879
+ select: todo('Select', 'bestax `Select` renders the `.select` wrapper and the `<select>` together'),
880
+ file: todo('File', 'bestax `File` renders the whole `.file-label` tree itself'),
725
881
  'file-label': part(),
726
882
  'file-input': part(),
727
883
  'file-cta': part(),
728
884
  'file-icon': part(),
729
885
  'file-name': part(),
730
- dropdown: todo('bestax `Dropdown` renders its own trigger and menu from props'),
886
+ dropdown: todo('Dropdown', 'bestax `Dropdown` renders its own trigger and menu from props'),
731
887
  'dropdown-menu': part(),
732
888
  'dropdown-content': part(),
733
889
  'dropdown-item': part(),
734
890
  'dropdown-divider': part(),
735
- icon: todo('bestax `Icon` renders its own `<i>` and adds an `aria-label`'),
736
- 'icon-text': todo('bestax `IconText` pairs with `Icon`, which renders its own `<i>`'),
737
- image: todo('bestax `Image` renders its own `<img>`'),
738
- menu: todo('bestax `Menu.Item` renders the `<li>` and the `<a>` together'),
891
+ icon: todo('Icon', 'bestax `Icon` renders its own `<i>` and adds an `aria-label`'),
892
+ 'icon-text': todo('IconText', 'bestax `IconText` pairs with `Icon`, which renders its own `<i>`'),
893
+ image: todo('Image', 'bestax `Image` renders its own `<img>`'),
894
+ menu: todo('Menu', 'bestax `Menu.Item` renders the `<li>` and the `<a>` together'),
739
895
  'menu-label': part(),
740
896
  'menu-list': part(),
741
897
  'menu-item': part(),
742
- message: todo('bestax `Message` always wraps its children in `.message-body`'),
898
+ message: todo('Message', 'bestax `Message` always wraps its children in `.message-body`'),
743
899
  'message-header': part(),
744
900
  'message-body': part(),
745
- modal: todo('bestax `Modal` renders its own background and content parts, and adds dialog attributes'),
901
+ modal: todo('Modal', 'bestax `Modal` renders its own background and content parts, and adds dialog attributes'),
746
902
  'modal-background': part(),
747
903
  'modal-content': part(),
748
904
  'modal-card': part(),
@@ -751,34 +907,24 @@ export const ROOTS = {
751
907
  'modal-card-body': part(),
752
908
  'modal-card-foot': part(),
753
909
  'modal-close': part(),
754
- navbar: todo('bestax `Navbar` adds navigation roles, and its burger renders its own spans'),
755
- 'navbar-brand': part(),
756
- 'navbar-burger': part(),
757
- 'navbar-menu': part(),
758
- 'navbar-start': part(),
759
- 'navbar-end': part(),
760
- 'navbar-item': part(),
761
- 'navbar-link': part(),
762
- 'navbar-dropdown': part(),
763
- 'navbar-divider': part(),
764
- 'navbar-content': part(),
765
- 'navbar-tabs': part(),
766
- pagination: todo('bestax `Pagination` renders its own list items and adds navigation roles'),
910
+ 'navbar-burger': todo('Navbar.Burger', 'bestax `Navbar.Burger` is a `<button>` that renders its own spans and `aria-expanded`, so rebuild the toggle with it, by hand'),
911
+ 'navbar-link': todo('Navbar.Link', 'inside a `Navbar.Dropdown`, bestax `Navbar.Link` adds `aria-haspopup`, `aria-expanded` and keyboard handling, so convert the `.has-dropdown` item and its link together, by hand'),
912
+ pagination: todo('Pagination', 'bestax `Pagination` renders its own list items and adds navigation roles'),
767
913
  'pagination-list': part(),
768
914
  'pagination-link': part(),
769
915
  'pagination-ellipsis': part(),
770
916
  'pagination-previous': part(),
771
917
  'pagination-next': part(),
772
- panel: todo('bestax `Panel` parts render their own tags and attributes (`Panel.Block` is an `<a>`, `Panel.Icon` adds an `aria-label`)'),
918
+ panel: todo('Panel', 'bestax `Panel` parts render their own tags and attributes (`Panel.Block` is an `<a>`, `Panel.Icon` adds an `aria-label`)'),
773
919
  'panel-heading': part(),
774
920
  'panel-tabs': part(),
775
921
  'panel-block': part(),
776
922
  'panel-icon': part(),
777
923
  'panel-list': part(),
778
- tabs: todo("bestax `Tabs` renders each tab's `<li>` and `<a>` together, with tab roles"),
779
- 'table-container': todo('bestax renders `.table-container` from `Table isResponsive`'),
780
- 'skeleton-block': todo('bestax `Skeleton` renders its own markup'),
781
- 'skeleton-lines': todo('bestax `Skeleton` renders its own markup'),
924
+ tabs: todo('Tabs', "bestax `Tabs` renders each tab's `<li>` and `<a>` together, with tab roles"),
925
+ 'table-container': todo('Table', 'bestax renders `.table-container` from `Table isResponsive`'),
926
+ 'skeleton-block': todo('Skeleton', 'bestax `Skeleton` renders its own markup'),
927
+ 'skeleton-lines': todo('Skeleton', 'bestax `Skeleton` renders its own markup'),
782
928
  // ---- Valid Bulma, nothing to convert to --------------------------------------
783
929
  // The other sources emit these on purpose where bestax has no component.
784
930
  help: plain('bestax renders `.help` only inside its form controls'),
@@ -786,14 +932,16 @@ export const ROOTS = {
786
932
  loader: plain("bestax `Loading` is an overlay, not Bulma's inline spinner"),
787
933
  'hero-buttons': plain('a layout class inside `.hero`; bestax has no part for it'),
788
934
  'hero-video': plain('a layout class inside `.hero`; bestax has no part for it'),
935
+ 'navbar-content': plain('a layout class inside `.navbar`; bestax has no part for it'),
936
+ 'navbar-tabs': plain('a layout class inside `.navbar`; bestax has no part for it'),
789
937
  'theme-dark': plain('a theme scope; bestax sets themes through `Theme`'),
790
938
  'theme-light': plain('a theme scope; bestax sets themes through `Theme`'),
791
939
  fa: plain("Font Awesome's own class, styled by Bulma inside `.icon`"),
792
940
  marginless: plain('a Bulma helper with no bestax prop'),
793
941
  paddingless: plain('a Bulma helper with no bestax prop'),
794
942
  };
795
- function todo(why) {
796
- return { status: 'todo', why };
943
+ function todo(target, why) {
944
+ return { status: 'todo', target, why };
797
945
  }
798
946
  function part() {
799
947
  return { status: 'todo', part: true };
@@ -824,6 +972,23 @@ export const PRECEDENCE = [
824
972
  'media-left',
825
973
  'media-content',
826
974
  'media-right',
975
+ // A part before a surface class, so the card around it still sees a part.
976
+ 'card',
977
+ 'card-header',
978
+ 'card-header-title',
979
+ 'card-header-icon',
980
+ 'card-image',
981
+ 'card-content',
982
+ 'card-footer',
983
+ 'card-footer-item',
984
+ 'navbar',
985
+ 'navbar-brand',
986
+ 'navbar-menu',
987
+ 'navbar-start',
988
+ 'navbar-end',
989
+ 'navbar-item',
990
+ 'navbar-dropdown',
991
+ 'navbar-divider',
827
992
  'buttons',
828
993
  'tags',
829
994
  'button',
@@ -839,7 +1004,12 @@ export const PRECEDENCE = [
839
1004
  'block',
840
1005
  ];
841
1006
  /** Refs reach the element only through these targets. */
842
- export const FORWARDS_REF = ['Button', 'Link'];
1007
+ export const FORWARDS_REF = [
1008
+ 'Button',
1009
+ 'Link',
1010
+ 'Navbar',
1011
+ 'Navbar.Item',
1012
+ ];
843
1013
  /**
844
1014
  * Plain tags bestax wraps, for an element with helper classes and no root
845
1015
  * (`<p className="has-text-centered">` → `<Paragraph textAlign="centered">`).
@@ -22,6 +22,12 @@ export interface ElementFacts {
22
22
  hasRef: boolean;
23
23
  /** Whether the element has children (JSX text, elements or expressions). */
24
24
  hasChildren: boolean;
25
+ /**
26
+ * The bestax components the element's child elements are, or convert to
27
+ * (`Card.Content`). Only a child written directly inside the element
28
+ * counts: one in an expression may not render.
29
+ */
30
+ childTargets?: readonly string[];
25
31
  /**
26
32
  * The component this element is the only child of, if any (`Link` for
27
33
  * `<Link href="/x"><a className="button">`), since that component can
@@ -1 +1 @@
1
- {"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../../../src/sources/bulma-classes/plan.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAiBH,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,2DAA2D;IAC3D,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1B;;;OAGG;IACH,UAAU,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC;IACtD,SAAS,EAAE,OAAO,CAAC;IACnB,MAAM,EAAE,OAAO,CAAC;IAChB,4EAA4E;IAC5E,WAAW,EAAE,OAAO,CAAC;IACrB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,IAAI;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,UAAU;IACzB,0CAA0C;IAC1C,MAAM,EAAE,MAAM,CAAC;IACf,6DAA6D;IAC7D,KAAK,EAAE,KAAK,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC;IACnD,4DAA4D;IAC5D,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,mEAAmE;IACnE,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,8DAA8D;IAC9D,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,IAAI;IACnB,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9B,KAAK,EAAE,IAAI,EAAE,CAAC;CACf;AA+BD,wBAAgB,IAAI,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI,CAgP9C"}
1
+ {"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../../../src/sources/bulma-classes/plan.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAiBH,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,2DAA2D;IAC3D,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1B;;;OAGG;IACH,UAAU,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC;IACtD,SAAS,EAAE,OAAO,CAAC;IACnB,MAAM,EAAE,OAAO,CAAC;IAChB,4EAA4E;IAC5E,WAAW,EAAE,OAAO,CAAC;IACrB;;;;OAIG;IACH,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,IAAI;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,UAAU;IACzB,0CAA0C;IAC1C,MAAM,EAAE,MAAM,CAAC;IACf,6DAA6D;IAC7D,KAAK,EAAE,KAAK,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC;IACnD,4DAA4D;IAC5D,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,mEAAmE;IACnE,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,8DAA8D;IAC9D,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,IAAI;IACnB,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9B,KAAK,EAAE,IAAI,EAAE,CAAC;CACf;AA+BD,wBAAgB,IAAI,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI,CAwQ9C"}
@@ -48,7 +48,7 @@ export function plan(facts) {
48
48
  todos.push({ rule: ruleId('legacy', token), message: hint });
49
49
  }
50
50
  // A family this source leaves as markup keeps its element as markup, even
51
- // beside a class it would convert (`card box`): the family's parts carry no
51
+ // beside a class it would convert (`navbar box`): the family's parts carry no
52
52
  // TODO of their own, so its outermost class is the one place it is flagged.
53
53
  const family = tokens.find(token => {
54
54
  const found = rootFor(token);
@@ -128,6 +128,16 @@ export function plan(facts) {
128
128
  if (entry.requiresChildren && !facts.hasChildren) {
129
129
  return refuse('children', target, `bestax \`${target}\` requires children, and this element has none; keep it as markup`);
130
130
  }
131
+ const wraps = entry.wrapsChildren;
132
+ if (wraps &&
133
+ (facts.hasChildren || wraps.whenEmpty) &&
134
+ !facts.childTargets?.some(child => wraps.unless.includes(child))) {
135
+ const parts = wraps.unless.map(part => `\`${part}\``);
136
+ const list = parts.length > 1
137
+ ? `${parts.slice(0, -1).join(', ')} or ${parts[parts.length - 1]}`
138
+ : parts[0];
139
+ return refuse('children', target, `bestax \`${target}\` renders its children inside a \`.${wraps.in}\` of its own unless one of them is a ${list}, so this element stays markup`);
140
+ }
131
141
  if (missing.length > 0) {
132
142
  const list = missing.map(([name, value]) => `\`${name}="${value}"\``);
133
143
  return refuse('defaults', target, `bestax \`${target}\` renders ${list.join(' and ')} when the element does not set ${missing.length === 1 ? 'it' : 'them'}; add ${missing.length === 1 ? 'it' : 'them'} here if that is what you want, then re-run`);
@@ -164,7 +174,7 @@ export function plan(facts) {
164
174
  converted.add(token);
165
175
  continue;
166
176
  }
167
- const helper = HELPER_TOKENS.get(token);
177
+ const helper = entry.noHelpers ? undefined : HELPER_TOKENS.get(token);
168
178
  if (!helper)
169
179
  continue;
170
180
  const prop = helper.group === 'text-color'
@@ -218,6 +228,9 @@ export function plan(facts) {
218
228
  props.push(['as', as]);
219
229
  props.push(...writes);
220
230
  const rest = tokens.filter(token => !converted.has(token));
231
+ if (entry.ownClassOnly && rest.length > 0) {
232
+ return refuse('attr', 'className', `bestax \`${target}\` drops its own class when it is given a \`className\`, so this element's other classes would take the place of \`.${root}\`; keep it as markup`);
233
+ }
221
234
  return {
222
235
  conversion: {
223
236
  target,
@@ -1 +1 @@
1
- {"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../../../src/sources/bulma-classes/rules.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAUH,eAAO,MAAM,KAAK,EAAE,WAAW,CAAC,MAAM,CAYpC,CAAC;AAqBH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEnD;AAED,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAK1D"}
1
+ {"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../../../src/sources/bulma-classes/rules.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAUH,eAAO,MAAM,KAAK,EAAE,WAAW,CAAC,MAAM,CAYpC,CAAC;AAuBH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEnD;AAED,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAK1D"}
@@ -41,6 +41,8 @@ const VOCABULARY = new Set([
41
41
  ...HELPER_PROPS,
42
42
  // Refused on every target, so no entry lists it.
43
43
  'dangerouslySetInnerHTML',
44
+ // Refused where the target drops its own class for one it is given.
45
+ 'className',
44
46
  ]);
45
47
  export function inVocabulary(token) {
46
48
  return VOCABULARY.has(token);
@@ -8,8 +8,9 @@
8
8
  *
9
9
  * Passes, in order:
10
10
  * 1. the shared stylesheet-import pass (a no-op under the default `keep`)
11
- * 2. plan every element in scope; a computed className is planned from the
12
- * strings in it and becomes a TODO, never a conversion
11
+ * 2. plan every element in scope, children before parents; a computed
12
+ * className is planned from the strings in it and becomes a TODO, never
13
+ * a conversion
13
14
  * 3. file-level gates: a non-React JSX runtime, CommonJS, styled-jsx, and a
14
15
  * Next.js server component (bestax's components are client components)
15
16
  * 4. TODOs in reading order; conversions innermost first
@@ -1 +1 @@
1
- {"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../../../src/sources/bulma-classes/transform.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAW,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE1D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAuRvD,MAAM,CAAC,OAAO,UAAU,SAAS,CAC/B,QAAQ,EAAE,QAAQ,EAClB,GAAG,EAAE,GAAG,EACR,OAAO,GAAE,gBAAqB,GAC7B,MAAM,GAAG,SAAS,CA4QpB"}
1
+ {"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../../../src/sources/bulma-classes/transform.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAW,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE1D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAmSvD,MAAM,CAAC,OAAO,UAAU,SAAS,CAC/B,QAAQ,EAAE,QAAQ,EAClB,GAAG,EAAE,GAAG,EACR,OAAO,GAAE,gBAAqB,GAC7B,MAAM,GAAG,SAAS,CA2SpB"}
@@ -8,8 +8,9 @@
8
8
  *
9
9
  * Passes, in order:
10
10
  * 1. the shared stylesheet-import pass (a no-op under the default `keep`)
11
- * 2. plan every element in scope; a computed className is planned from the
12
- * strings in it and becomes a TODO, never a conversion
11
+ * 2. plan every element in scope, children before parents; a computed
12
+ * className is planned from the strings in it and becomes a TODO, never
13
+ * a conversion
13
14
  * 3. file-level gates: a non-React JSX runtime, CommonJS, styled-jsx, and a
14
15
  * Next.js server component (bestax's components are client components)
15
16
  * 4. TODOs in reading order; conversions innermost first
@@ -17,7 +18,7 @@
17
18
  */
18
19
  import path from 'node:path';
19
20
  import { addTodo, findAttr, forgetJsxParens, jsxNameParts, literalValueOf, makeAttr, renameElement, reprintDirectives, } from '../_shared/jsx-utils.js';
20
- import { collectBoundNames, makeReserve, prefersTabs, } from '../_shared/imports.js';
21
+ import { collectBoundNames, makeReserve, prefersTabs, resolvesToBinding, } from '../_shared/imports.js';
21
22
  import { rewriteStylesheetImports } from '../_shared/css-imports.js';
22
23
  import { BESTAX, buildBestaxImport, placeBestaxImport, seedBestaxImport, } from '../_shared/bestax-import.js';
23
24
  import { plan } from './plan.js';
@@ -217,6 +218,17 @@ function isContent(child) {
217
218
  }
218
219
  return true;
219
220
  }
221
+ /**
222
+ * Whether a JSX child reaches React at all. JSX drops a comment, and text
223
+ * made only of spaces and tabs split by a line break; it keeps the rest.
224
+ * `<div> </div>` has a child, which `Card` would wrap, and so does an
225
+ * `&nbsp;` on its own line: `trim()` would drop it, and JSX does not.
226
+ */
227
+ function reachesReact(child) {
228
+ if (child.type !== 'JSXText')
229
+ return isContent(child);
230
+ return /[^ \t\r\n]/.test(child.value) || !/[\r\n]/.test(child.value);
231
+ }
220
232
  function insideForeignContent(elementPath) {
221
233
  let current = elementPath.parent;
222
234
  while (current) {
@@ -278,29 +290,55 @@ export default function transform(fileInfo, api, options = {}) {
278
290
  // every class a converted element renders.
279
291
  rewriteStylesheetImports(ctx, root, options.cssMode ?? 'keep');
280
292
  // ---- 2. Plan the elements in scope ----------------------------------------
281
- const bestaxLocals = new Set();
293
+ // Each local bestax name, with the component path it stands for: `Card`
294
+ // for `import { Card as C }`, nothing for a namespace (`B.Card`).
295
+ const bestaxNames = new Map();
282
296
  root
283
297
  .find(j.ImportDeclaration, { source: { value: BESTAX } })
284
298
  .forEach((importPath) => {
285
299
  for (const spec of importPath.node.specifiers ?? []) {
286
- if (spec.local)
287
- bestaxLocals.add(spec.local.name);
300
+ if (!spec.local)
301
+ continue;
302
+ bestaxNames.set(spec.local.name, spec.type === 'ImportSpecifier' ? [spec.imported.name] : []);
288
303
  }
289
304
  });
305
+ const bestaxLocals = new Set(bestaxNames.keys());
306
+ // Children are planned before their parents (reverse document order), so
307
+ // a parent can see what its children become: `Card` converts only beside
308
+ // a child that is one of its parts.
309
+ const planned = new Map();
310
+ const programScope = root.find(j.Program).paths()[0]?.scope;
311
+ const childTargets = (elementPath) => (elementPath.node.children ?? []).flatMap((child) => {
312
+ if (child.type !== 'JSXElement')
313
+ return [];
314
+ const target = planned.get(child)?.conversion?.target;
315
+ if (target)
316
+ return [target];
317
+ // A part the file already uses counts when its name is the bestax
318
+ // import, not a local that shadows it. A direct child is looked up in
319
+ // the element's own scope, which is the child's.
320
+ const parts = jsxNameParts(child.openingElement.name);
321
+ const owner = parts && bestaxNames.get(parts[0]);
322
+ if (!owner || !resolvesToBinding(elementPath, parts[0], programScope)) {
323
+ return [];
324
+ }
325
+ return [[...owner, ...parts.slice(1)].join('.')];
326
+ });
290
327
  // `converts` is whether the element would become a component with its
291
328
  // classes written out, so it holds for a computed className too.
292
329
  const elements = [];
293
- root.find(j.JSXElement).forEach(elementPath => {
330
+ const candidates = root.find(j.JSXElement).paths();
331
+ for (const elementPath of [...candidates].reverse()) {
294
332
  const element = elementPath.node;
295
333
  const name = element.openingElement.name;
296
334
  if (name.type !== 'JSXIdentifier' || !INTRINSIC.test(name.name))
297
- return;
335
+ continue;
298
336
  if (FOREIGN_ROOTS.has(name.name) || insideForeignContent(elementPath)) {
299
- return;
337
+ continue;
300
338
  }
301
339
  const classAttr = findAttr(element, 'className');
302
340
  if (!classAttr)
303
- return;
341
+ continue;
304
342
  const attributes = new Map();
305
343
  for (const attr of element.openingElement.attributes ?? []) {
306
344
  if (attr.type !== 'JSXAttribute' || attr === classAttr)
@@ -316,7 +354,8 @@ export default function transform(fileInfo, api, options = {}) {
316
354
  attributes,
317
355
  hasSpread: (element.openingElement.attributes ?? []).some((attr) => attr.type === 'JSXSpreadAttribute'),
318
356
  hasRef: attributes.has('ref'),
319
- hasChildren: (element.children ?? []).some(isContent),
357
+ hasChildren: (element.children ?? []).some(reachesReact),
358
+ childTargets: childTargets(elementPath),
320
359
  onlyChildOf: onlyChildOf(elementPath, bestaxLocals),
321
360
  };
322
361
  let result = plan(facts);
@@ -336,10 +375,11 @@ export default function transform(fileInfo, api, options = {}) {
336
375
  ],
337
376
  };
338
377
  }
378
+ planned.set(element, result);
339
379
  if (result.conversion || result.todos.length > 0) {
340
- elements.push({ path: elementPath, plan: result, converts });
380
+ elements.unshift({ path: elementPath, plan: result, converts });
341
381
  }
342
- });
382
+ }
343
383
  if (elements.length === 0)
344
384
  return print();
345
385
  // ---- 3. File-level gates ------------------------------------------------------
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bestax-migrate",
3
- "version": "2.4.0",
3
+ "version": "2.6.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": {