bestax-migrate 2.1.6 → 2.2.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.
- package/README.md +13 -8
- package/dist/cli.d.ts +53 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +159 -9
- package/dist/sources/_shared/imports.d.ts +46 -0
- package/dist/sources/_shared/imports.d.ts.map +1 -0
- package/dist/sources/_shared/imports.js +152 -0
- package/dist/sources/{react-bulma-components → _shared}/jsx-utils.d.ts +20 -12
- package/dist/sources/_shared/jsx-utils.d.ts.map +1 -0
- package/dist/sources/{react-bulma-components → _shared}/jsx-utils.js +15 -10
- package/dist/sources/_shared/make-styles-transform.d.ts +37 -0
- package/dist/sources/_shared/make-styles-transform.d.ts.map +1 -0
- package/dist/sources/_shared/make-styles-transform.js +440 -0
- package/dist/sources/_shared/props.d.ts +20 -0
- package/dist/sources/_shared/props.d.ts.map +1 -0
- package/dist/sources/{react-bulma-components → _shared}/props.js +11 -7
- package/dist/sources/_shared/specials-utils.d.ts +45 -0
- package/dist/sources/_shared/specials-utils.d.ts.map +1 -0
- package/dist/sources/_shared/specials-utils.js +124 -0
- package/dist/sources/rbx/deps.d.ts +18 -0
- package/dist/sources/rbx/deps.d.ts.map +1 -0
- package/dist/sources/rbx/deps.js +287 -0
- package/dist/sources/rbx/index.d.ts +3 -0
- package/dist/sources/rbx/index.d.ts.map +1 -0
- package/dist/sources/rbx/index.js +10 -0
- package/dist/sources/rbx/mapping.d.ts +49 -0
- package/dist/sources/rbx/mapping.d.ts.map +1 -0
- package/dist/sources/rbx/mapping.js +924 -0
- package/dist/sources/rbx/responsive.d.ts +30 -0
- package/dist/sources/rbx/responsive.d.ts.map +1 -0
- package/dist/sources/rbx/responsive.js +279 -0
- package/dist/sources/rbx/specials.d.ts +21 -0
- package/dist/sources/rbx/specials.d.ts.map +1 -0
- package/dist/sources/rbx/specials.js +706 -0
- package/dist/sources/rbx/styles.d.ts +21 -0
- package/dist/sources/rbx/styles.d.ts.map +1 -0
- package/dist/sources/rbx/styles.js +29 -0
- package/dist/sources/rbx/transform.d.ts +25 -0
- package/dist/sources/rbx/transform.d.ts.map +1 -0
- package/dist/sources/rbx/transform.js +911 -0
- package/dist/sources/react-bulma-components/deps.d.ts.map +1 -1
- package/dist/sources/react-bulma-components/deps.js +16 -0
- package/dist/sources/react-bulma-components/responsive.d.ts +1 -1
- package/dist/sources/react-bulma-components/responsive.d.ts.map +1 -1
- package/dist/sources/react-bulma-components/responsive.js +1 -1
- package/dist/sources/react-bulma-components/specials.d.ts +3 -9
- package/dist/sources/react-bulma-components/specials.d.ts.map +1 -1
- package/dist/sources/react-bulma-components/specials.js +4 -91
- package/dist/sources/react-bulma-components/styles.d.ts +9 -9
- package/dist/sources/react-bulma-components/styles.d.ts.map +1 -1
- package/dist/sources/react-bulma-components/styles.js +17 -389
- package/dist/sources/react-bulma-components/transform.d.ts.map +1 -1
- package/dist/sources/react-bulma-components/transform.js +378 -101
- package/dist/sources/registry.d.ts.map +1 -1
- package/dist/sources/registry.js +2 -0
- package/dist/types.d.ts +8 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +8 -7
- package/dist/sources/react-bulma-components/jsx-utils.d.ts.map +0 -1
- package/dist/sources/react-bulma-components/props.d.ts +0 -15
- package/dist/sources/react-bulma-components/props.d.ts.map +0 -1
|
@@ -0,0 +1,706 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structural handlers for rbx → bestax conversions a rename table cannot
|
|
3
|
+
* express: targets chosen by a prop's value (Level.Item align, Pagination.Step
|
|
4
|
+
* align), container collapsing (Select.Container, Image.Container,
|
|
5
|
+
* Modal.Container), and replacement with plain HTML where bestax has no
|
|
6
|
+
* component (Heading, Help, Label, Select.Option).
|
|
7
|
+
*
|
|
8
|
+
* The two genuinely new families relative to react-bulma-components are here
|
|
9
|
+
* too, though they are driven from transform.ts rather than the mapping table
|
|
10
|
+
* because they are helper *props* that can sit on any element at all:
|
|
11
|
+
* `badge*` and `tooltip*` become wrapping bestax components.
|
|
12
|
+
*
|
|
13
|
+
* A handler may return a `target` override for the rename step, or mark the
|
|
14
|
+
* element `replaced` when it substituted the node itself.
|
|
15
|
+
*/
|
|
16
|
+
import { BADGE_PROPS, RESPONSIVE_BREAKPOINTS, TOOLTIP_PROPS, UNIVERSAL_PROPS, } from './mapping.js';
|
|
17
|
+
import { addAttr, addTodo, buildJsxName, attributesOf, findAttr, jsxNameParts, literalValueOf, makeAttr, plainElement, removeAttr, resolveBooleanish, } from '../_shared/jsx-utils.js';
|
|
18
|
+
import { alignTarget, makeStripModifierProps, mergeClassName, parseIconClasses, } from '../_shared/specials-utils.js';
|
|
19
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
20
|
+
const stripModifierProps = makeStripModifierProps(UNIVERSAL_PROPS, RESPONSIVE_BREAKPOINTS);
|
|
21
|
+
/**
|
|
22
|
+
* Turn a literal modifier prop into an `is-*` class fragment for an element
|
|
23
|
+
* on its way to becoming plain HTML. Returns the class (or undefined) and
|
|
24
|
+
* always removes the attribute.
|
|
25
|
+
*/
|
|
26
|
+
function modifierClass(ctx, path, element, prop, base, where) {
|
|
27
|
+
const attr = findAttr(element, prop);
|
|
28
|
+
if (!attr)
|
|
29
|
+
return base;
|
|
30
|
+
const literal = literalValueOf(attr);
|
|
31
|
+
removeAttr(element, attr);
|
|
32
|
+
if (literal.kind === 'string' || literal.kind === 'number') {
|
|
33
|
+
return `${base} is-${literal.value}`;
|
|
34
|
+
}
|
|
35
|
+
addTodo(ctx, path, `prop:${prop}`, `dynamic ${where} \`${prop}\`; add the matching is-* class by hand`);
|
|
36
|
+
return base;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Also strips the universal `responsive` object: responsive.ts would have
|
|
40
|
+
* consumed it, but a `replaced: true` special skips that pass too, and it is
|
|
41
|
+
* deliberately absent from UNIVERSAL_PROPS so stripModifierProps misses it.
|
|
42
|
+
* Left behind it is an object literal on an intrinsic element — which does
|
|
43
|
+
* not compile, unlike the badge/tooltip case which merely warns.
|
|
44
|
+
*
|
|
45
|
+
* rbx's badge/tooltip helper props become wrapping components in
|
|
46
|
+
* transform.ts, but that pass runs after the rename step — which a
|
|
47
|
+
* `replaced: true` special skips. So a plain-element rewrite has to account
|
|
48
|
+
* for them itself, or they ride onto the HTML tag as unknown DOM attributes.
|
|
49
|
+
*/
|
|
50
|
+
function stripHelperComponentProps(ctx, path, attrs, where) {
|
|
51
|
+
const kept = [];
|
|
52
|
+
const dropped = [];
|
|
53
|
+
for (const attr of attrs) {
|
|
54
|
+
const name = attr?.name?.name;
|
|
55
|
+
if (name &&
|
|
56
|
+
(name in BADGE_PROPS || name in TOOLTIP_PROPS || name === 'responsive')) {
|
|
57
|
+
dropped.push(name);
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
kept.push(attr);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (dropped.length > 0) {
|
|
64
|
+
addTodo(ctx, path, 'plain-element', `${where} became a plain element, so the ${dropped
|
|
65
|
+
.map(d => `\`${d}\``)
|
|
66
|
+
.join(', ')} helper prop(s) were dropped — re-apply by hand (badge/tooltip as a wrapping \`<Badge>\`/\`<Tooltip>\`, responsive settings as classes)`);
|
|
67
|
+
}
|
|
68
|
+
return kept;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* rbx puts `as` on every component, but a handler that chooses its target from
|
|
72
|
+
* a prop value can land on a bestax component that has no `as` — Media.Item
|
|
73
|
+
* resolves to Left/Content/Right where only Left declares one, Level.Item to
|
|
74
|
+
* Left/Right/Item where only Item does, Navbar.Item to Item/Dropdown where
|
|
75
|
+
* only Item does. Opting into `as` in the mapping table cannot express that,
|
|
76
|
+
* so the target has to be checked after it is picked.
|
|
77
|
+
*/
|
|
78
|
+
function restrictAsToTargets(ctx, path, element, target, allowed) {
|
|
79
|
+
const asAttr = findAttr(element, 'as');
|
|
80
|
+
if (!asAttr || (target && allowed.includes(target)))
|
|
81
|
+
return;
|
|
82
|
+
removeAttr(element, asAttr);
|
|
83
|
+
addTodo(ctx, path, 'prop:as', `bestax's \`${target}\` has no \`as\` prop (of the targets this can resolve to, only ${allowed
|
|
84
|
+
.map(a => `\`${a}\``)
|
|
85
|
+
.join(' / ')} does); render the tag directly or restructure`);
|
|
86
|
+
ctx.dirty = true;
|
|
87
|
+
}
|
|
88
|
+
/** Replace the element with a plain HTML tag carrying `className`. */
|
|
89
|
+
function replaceWithPlain(ctx, path, element, tag, className, where) {
|
|
90
|
+
const merged = mergeClassName(ctx, path, element, className, where);
|
|
91
|
+
const rest = stripHelperComponentProps(ctx, path, stripModifierProps(ctx, path, attributesOf(element), where), where);
|
|
92
|
+
path.replace(plainElement(ctx.j, tag, merged, rest, element.children ?? []));
|
|
93
|
+
ctx.dirty = true;
|
|
94
|
+
return { replaced: true };
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* rbx wraps several components in a `*.Container` that owns the modifiers,
|
|
98
|
+
* where bestax's component renders that wrapper itself. Move the container's
|
|
99
|
+
* (already-converted) attributes onto its single child and replace the
|
|
100
|
+
* container with it — otherwise both would rename to the same bestax
|
|
101
|
+
* component and nest, which is invalid.
|
|
102
|
+
*
|
|
103
|
+
* Returns null when the element isn't the single-JSX-child shape, so the
|
|
104
|
+
* caller can fall back to keeping the container.
|
|
105
|
+
*/
|
|
106
|
+
function collapseOntoChild(ctx, path, element, where, expected) {
|
|
107
|
+
const children = (element.children ?? []).filter((c) => !(c.type === 'JSXText' && c.value.trim() === ''));
|
|
108
|
+
if (children.length !== 1 || children[0].type !== 'JSXElement')
|
|
109
|
+
return null;
|
|
110
|
+
const child = children[0];
|
|
111
|
+
// The child must be the component this container exists to wrap. rbx lets
|
|
112
|
+
// both of these containers hold something else — an `<iframe>` for a ratio
|
|
113
|
+
// box, a native `<select>` — and folding onto that put Bulma modifier props
|
|
114
|
+
// on an intrinsic element and dropped the wrapper the markup needs.
|
|
115
|
+
const childPath = ctx.resolve?.(child.openingElement?.name);
|
|
116
|
+
if (!childPath || childPath.join('.') !== expected)
|
|
117
|
+
return null;
|
|
118
|
+
for (const attr of [...attributesOf(element)]) {
|
|
119
|
+
const name = attr.name.name;
|
|
120
|
+
if (findAttr(child, name)) {
|
|
121
|
+
addTodo(ctx, path, `prop:${name}`, `\`${where}\` folded into its child, but both set \`${name}\`; the child's value was kept — reconcile by hand`);
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
removeAttr(element, attr);
|
|
125
|
+
addAttr(child, attr);
|
|
126
|
+
}
|
|
127
|
+
path.replace(child);
|
|
128
|
+
ctx.dirty = true;
|
|
129
|
+
return { replaced: true };
|
|
130
|
+
}
|
|
131
|
+
const SPECIALS = {
|
|
132
|
+
/**
|
|
133
|
+
* rbx's `Heading` is Bulma's `.heading` label (small caps), not a title —
|
|
134
|
+
* bestax has no component for it, so it becomes a plain <p>.
|
|
135
|
+
*/
|
|
136
|
+
heading(ctx, path, element) {
|
|
137
|
+
return replaceWithPlain(ctx, path, element, 'p', 'heading', 'Heading');
|
|
138
|
+
},
|
|
139
|
+
/**
|
|
140
|
+
* rbx folds title and subtitle into one component behind a `subtitle`
|
|
141
|
+
* boolean; bestax splits them into `Title` and `SubTitle`. rbx also ignores
|
|
142
|
+
* `spaced` when `subtitle` is set, so a subtitle never carries isSpaced.
|
|
143
|
+
*/
|
|
144
|
+
title(ctx, path, element) {
|
|
145
|
+
const subtitleAttr = findAttr(element, 'subtitle');
|
|
146
|
+
let target = 'Title';
|
|
147
|
+
let isSubtitle = false;
|
|
148
|
+
if (subtitleAttr) {
|
|
149
|
+
const resolved = resolveBooleanish(subtitleAttr);
|
|
150
|
+
if (resolved === 'expression') {
|
|
151
|
+
addTodo(ctx, path, 'prop:subtitle', 'dynamic `subtitle`; bestax splits these into `<Title>` and `<SubTitle>` — pick one by hand');
|
|
152
|
+
}
|
|
153
|
+
else if (resolved === 'truthy') {
|
|
154
|
+
target = 'SubTitle';
|
|
155
|
+
isSubtitle = true;
|
|
156
|
+
}
|
|
157
|
+
removeAttr(element, subtitleAttr);
|
|
158
|
+
ctx.dirty = true;
|
|
159
|
+
}
|
|
160
|
+
const spacedAttr = findAttr(element, 'spaced');
|
|
161
|
+
if (spacedAttr) {
|
|
162
|
+
const resolved = resolveBooleanish(spacedAttr);
|
|
163
|
+
// rbx ignores `spaced` on a subtitle, so a SubTitle drops it outright.
|
|
164
|
+
if (isSubtitle) {
|
|
165
|
+
removeAttr(element, spacedAttr);
|
|
166
|
+
}
|
|
167
|
+
else if (resolved === 'truthy') {
|
|
168
|
+
removeAttr(element, spacedAttr);
|
|
169
|
+
addAttr(element, makeAttr(ctx.j, 'isSpaced'));
|
|
170
|
+
}
|
|
171
|
+
else if (resolved === 'falsy') {
|
|
172
|
+
removeAttr(element, spacedAttr);
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
// Dynamic: keep the expression, just rename the prop.
|
|
176
|
+
spacedAttr.name = ctx.j.jsxIdentifier('isSpaced');
|
|
177
|
+
}
|
|
178
|
+
ctx.dirty = true;
|
|
179
|
+
}
|
|
180
|
+
return { target, handledProps: ['subtitle', 'spaced'] };
|
|
181
|
+
},
|
|
182
|
+
/**
|
|
183
|
+
* bestax's Icon takes a `name` (plus `library`/`variant`) instead of an
|
|
184
|
+
* icon-font <i> child. Parse the child when we can read it, otherwise keep
|
|
185
|
+
* the element and explain.
|
|
186
|
+
*/
|
|
187
|
+
icon(ctx, path, element) {
|
|
188
|
+
const children = (element.children ?? []).filter((c) => !(c.type === 'JSXText' && c.value.trim() === ''));
|
|
189
|
+
const iChild = children.find((c) => c.type === 'JSXElement' &&
|
|
190
|
+
c.openingElement?.name?.type === 'JSXIdentifier' &&
|
|
191
|
+
(c.openingElement.name.name === 'i' ||
|
|
192
|
+
c.openingElement.name.name === 'span'));
|
|
193
|
+
if (children.length === 1 && iChild) {
|
|
194
|
+
const classAttr = (iChild.openingElement.attributes ?? []).find((a) => a.type === 'JSXAttribute' && a.name.name === 'className');
|
|
195
|
+
const literal = classAttr ? literalValueOf(classAttr) : undefined;
|
|
196
|
+
if (literal?.kind === 'string') {
|
|
197
|
+
const parsed = parseIconClasses(literal.value);
|
|
198
|
+
if (parsed) {
|
|
199
|
+
addAttr(element, makeAttr(ctx.j, 'name', parsed.name));
|
|
200
|
+
if (parsed.library) {
|
|
201
|
+
addAttr(element, makeAttr(ctx.j, 'library', parsed.library));
|
|
202
|
+
}
|
|
203
|
+
if (parsed.variant) {
|
|
204
|
+
addAttr(element, makeAttr(ctx.j, 'variant', parsed.variant));
|
|
205
|
+
}
|
|
206
|
+
element.children = [];
|
|
207
|
+
if (element.openingElement) {
|
|
208
|
+
element.openingElement.selfClosing = true;
|
|
209
|
+
element.closingElement = null;
|
|
210
|
+
}
|
|
211
|
+
ctx.dirty = true;
|
|
212
|
+
return {};
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
// rbx's own docs teach `<Icon><FontAwesomeIcon icon={faHome} /></Icon>`,
|
|
217
|
+
// so this is the shape most rbx code is in. There is no icon name to read
|
|
218
|
+
// out of a component reference, and `name` is required on bestax's Icon.
|
|
219
|
+
addTodo(ctx, path, 'component:Icon', 'bestax `Icon` takes a required `name` (plus optional `library`/`variant`) instead of an icon child — e.g. `<FontAwesomeIcon icon={faHome} />` becomes `<Icon name="home" library="fa" variant="solid" />`');
|
|
220
|
+
return {};
|
|
221
|
+
},
|
|
222
|
+
/**
|
|
223
|
+
* rbx's Divider rendered its children as a centred label (`div.is-divider`
|
|
224
|
+
* with the text in `data-content`). bestax's Divider is a bare `<hr>` and
|
|
225
|
+
* takes no children -- React rejects children on a void element at runtime,
|
|
226
|
+
* so passing them through produced a crash with no TODO naming the lost
|
|
227
|
+
* label. The children are left in place so nothing is discarded silently;
|
|
228
|
+
* the TODO tells the user where the label has to go instead.
|
|
229
|
+
*/
|
|
230
|
+
divider(ctx, path, element) {
|
|
231
|
+
const children = (element.children ?? []).filter((c) => !(c.type === 'JSXText' && c.value.trim() === ''));
|
|
232
|
+
if (children.length > 0) {
|
|
233
|
+
addTodo(ctx, path, 'component:Divider', 'rbx `Divider` rendered its children as a centred label; bestax `Divider` is a bare `<hr>` and takes no children — move the label into surrounding markup, or drop it');
|
|
234
|
+
}
|
|
235
|
+
return {};
|
|
236
|
+
},
|
|
237
|
+
/**
|
|
238
|
+
* rbx nests <Image> inside <Image.Container>, which carries the size; the
|
|
239
|
+
* bestax Image renders the <figure> itself, so the container collapses and
|
|
240
|
+
* hands its size down.
|
|
241
|
+
*/
|
|
242
|
+
'image-container'(ctx, path, element) {
|
|
243
|
+
const sizeAttr = findAttr(element, 'size');
|
|
244
|
+
if (sizeAttr) {
|
|
245
|
+
const literal = literalValueOf(sizeAttr);
|
|
246
|
+
if (literal.kind === 'number') {
|
|
247
|
+
// rbx takes a pixel dimension (64); bestax takes Bulma's `is-64x64`
|
|
248
|
+
// square form, so the number becomes "NxN".
|
|
249
|
+
sizeAttr.value = ctx.j.stringLiteral(`${literal.value}x${literal.value}`);
|
|
250
|
+
ctx.dirty = true;
|
|
251
|
+
}
|
|
252
|
+
else if (literal.kind !== 'string') {
|
|
253
|
+
addTodo(ctx, path, 'prop:size', 'dynamic `Image.Container` size; set `size` on the bestax `<Image>` by hand');
|
|
254
|
+
}
|
|
255
|
+
// A string literal is already a ratio like "16by9" — pass it through.
|
|
256
|
+
}
|
|
257
|
+
const collapsed = collapseOntoChild(ctx, path, element, 'Image.Container', 'Image');
|
|
258
|
+
if (collapsed)
|
|
259
|
+
return collapsed;
|
|
260
|
+
// No single child to fold into — keep the container as the Image itself.
|
|
261
|
+
return { target: 'Image', handledProps: ['size'] };
|
|
262
|
+
},
|
|
263
|
+
/**
|
|
264
|
+
* rbx's `Loader` is Bulma's plain `.loader` spinner, which always renders.
|
|
265
|
+
* bestax's `Loading` is a different thing — a dismissible overlay that
|
|
266
|
+
* returns null unless `active` — so this emits the element rbx did.
|
|
267
|
+
*/
|
|
268
|
+
loader(ctx, path, element) {
|
|
269
|
+
return replaceWithPlain(ctx, path, element, 'div', 'loader', 'Loader');
|
|
270
|
+
},
|
|
271
|
+
/** rbx's full-screen PageLoader is bestax's Loading with isFullPage. */
|
|
272
|
+
'page-loader'(ctx, _path, element) {
|
|
273
|
+
if (!findAttr(element, 'isFullPage')) {
|
|
274
|
+
addAttr(element, makeAttr(ctx.j, 'isFullPage'));
|
|
275
|
+
ctx.dirty = true;
|
|
276
|
+
}
|
|
277
|
+
return { target: 'Loading' };
|
|
278
|
+
},
|
|
279
|
+
/**
|
|
280
|
+
* rbx's Field `multiline` only means anything alongside `kind="group"`, and
|
|
281
|
+
* bestax folds both into one prop: `grouped="multiline"`. So this has to
|
|
282
|
+
* win over the `kind` mapping rather than sit beside it — `grouped` set
|
|
283
|
+
* twice would be invalid JSX.
|
|
284
|
+
*/
|
|
285
|
+
field(ctx, path, element) {
|
|
286
|
+
const multilineAttr = findAttr(element, 'multiline');
|
|
287
|
+
if (!multilineAttr)
|
|
288
|
+
return {};
|
|
289
|
+
const resolved = resolveBooleanish(multilineAttr);
|
|
290
|
+
removeAttr(element, multilineAttr);
|
|
291
|
+
if (resolved === 'truthy') {
|
|
292
|
+
// rbx applies `is-grouped-multiline` only when kind === "group"
|
|
293
|
+
// (`[`${k}-multiline`]: k === "is-grouped" && multiline === true`), so
|
|
294
|
+
// consuming `kind` unconditionally dropped `has-addons` from
|
|
295
|
+
// `<Field kind="addons" multiline>` and added a modifier rbx would not
|
|
296
|
+
// have rendered.
|
|
297
|
+
const kindAttr = findAttr(element, 'kind');
|
|
298
|
+
const kindLiteral = kindAttr ? literalValueOf(kindAttr) : undefined;
|
|
299
|
+
const isGroup = kindLiteral?.kind === 'string' && kindLiteral.value === 'group';
|
|
300
|
+
// Note `!kindAttr` is NOT grouped in here: with no `kind` at all, rbx's
|
|
301
|
+
// `k` is undefined, so `multiline` renders nothing and the field stays a
|
|
302
|
+
// plain block. Treating it as group turned it into a flex row.
|
|
303
|
+
if (isGroup) {
|
|
304
|
+
if (kindAttr)
|
|
305
|
+
removeAttr(element, kindAttr);
|
|
306
|
+
const existing = findAttr(element, 'grouped');
|
|
307
|
+
if (existing)
|
|
308
|
+
removeAttr(element, existing);
|
|
309
|
+
addAttr(element, makeAttr(ctx.j, 'grouped', 'multiline'));
|
|
310
|
+
ctx.dirty = true;
|
|
311
|
+
return { handledProps: ['multiline', 'kind'] };
|
|
312
|
+
}
|
|
313
|
+
// kind="addons", a dynamic kind, or no kind at all: rbx ignores
|
|
314
|
+
// multiline in every one of those, so the codemod does too and leaves
|
|
315
|
+
// `kind` for the mapping table.
|
|
316
|
+
if (kindAttr && kindLiteral?.kind === 'expression') {
|
|
317
|
+
addTodo(ctx, path, 'prop:multiline', 'dynamic Field `kind` alongside `multiline`; rbx applies multiline only when kind is "group" — set `grouped="multiline"` by hand if that branch is reachable');
|
|
318
|
+
}
|
|
319
|
+
ctx.dirty = true;
|
|
320
|
+
return { handledProps: ['multiline'] };
|
|
321
|
+
}
|
|
322
|
+
if (resolved === 'expression') {
|
|
323
|
+
addTodo(ctx, path, 'prop:multiline', 'dynamic Field `multiline`; set `grouped="multiline"` conditionally by hand');
|
|
324
|
+
}
|
|
325
|
+
ctx.dirty = true;
|
|
326
|
+
return { handledProps: ['multiline'] };
|
|
327
|
+
},
|
|
328
|
+
/** bestax has no Help component; Bulma's markup is a plain <p class="help">. */
|
|
329
|
+
help(ctx, path, element) {
|
|
330
|
+
const className = modifierClass(ctx, path, element, 'color', 'help', 'Help');
|
|
331
|
+
return replaceWithPlain(ctx, path, element, 'p', className, 'Help');
|
|
332
|
+
},
|
|
333
|
+
/** bestax has no standalone Label; Bulma's markup is <label class="label">. */
|
|
334
|
+
label(ctx, path, element) {
|
|
335
|
+
let className = modifierClass(ctx, path, element, 'size', 'label', 'Label');
|
|
336
|
+
const disabledAttr = findAttr(element, 'disabled');
|
|
337
|
+
if (disabledAttr) {
|
|
338
|
+
// By value, not by presence: `disabled={false}` must not become a
|
|
339
|
+
// permanent `is-disabled`, and a dynamic value cannot be baked into a
|
|
340
|
+
// static class string.
|
|
341
|
+
const resolved = resolveBooleanish(disabledAttr);
|
|
342
|
+
removeAttr(element, disabledAttr);
|
|
343
|
+
if (resolved === 'truthy') {
|
|
344
|
+
className = `${className} is-disabled`;
|
|
345
|
+
}
|
|
346
|
+
else if (resolved === 'expression') {
|
|
347
|
+
addTodo(ctx, path, 'prop:disabled', "dynamic Label `disabled`; set className={disabled ? 'label is-disabled' : 'label'} by hand");
|
|
348
|
+
}
|
|
349
|
+
ctx.dirty = true;
|
|
350
|
+
}
|
|
351
|
+
return replaceWithPlain(ctx, path, element, 'label', className, 'Label');
|
|
352
|
+
},
|
|
353
|
+
/**
|
|
354
|
+
* rbx wraps <Select> in <Select.Container>, which owns the modifiers; the
|
|
355
|
+
* bestax Select renders its own wrapper, so the container collapses onto it.
|
|
356
|
+
*/
|
|
357
|
+
'select-container'(ctx, path, element) {
|
|
358
|
+
// Decide the plain-markup fallback FIRST, while the props are still rbx's
|
|
359
|
+
// own — converting them to bestax booleans and then emitting a <div>
|
|
360
|
+
// stranded things like `isRounded` on an intrinsic element.
|
|
361
|
+
const allKids = (element.children ?? []).filter((c) => !(c.type === 'JSXText' && c.value.trim() === ''));
|
|
362
|
+
if (allKids.length !== 1) {
|
|
363
|
+
// bestax's `Select` renders exactly one `<select>`, so a container
|
|
364
|
+
// holding several has no direct equivalent. rbx's own markup — a
|
|
365
|
+
// `div.select` wrapper — is valid whatever the children are.
|
|
366
|
+
let cls = 'select';
|
|
367
|
+
for (const prop of ['size', 'color', 'state']) {
|
|
368
|
+
cls = modifierClass(ctx, path, element, prop, cls, 'Select.Container');
|
|
369
|
+
}
|
|
370
|
+
for (const [prop, klass] of [
|
|
371
|
+
['fullwidth', 'is-fullwidth'],
|
|
372
|
+
['rounded', 'is-rounded'],
|
|
373
|
+
]) {
|
|
374
|
+
const attr = findAttr(element, prop);
|
|
375
|
+
if (!attr)
|
|
376
|
+
continue;
|
|
377
|
+
const resolved = resolveBooleanish(attr);
|
|
378
|
+
if (resolved === 'truthy') {
|
|
379
|
+
cls = `${cls} ${klass}`;
|
|
380
|
+
}
|
|
381
|
+
else if (resolved === 'expression') {
|
|
382
|
+
// A plain element cannot carry the condition, so say so rather than
|
|
383
|
+
// dropping the styling along with its expression.
|
|
384
|
+
addTodo(ctx, path, `prop:${prop}`, `dynamic \`${prop}\` on a Select.Container that became plain markup; apply \`${klass}\` conditionally via className by hand`);
|
|
385
|
+
}
|
|
386
|
+
removeAttr(element, attr);
|
|
387
|
+
ctx.dirty = true;
|
|
388
|
+
}
|
|
389
|
+
addTodo(ctx, path, 'component:Select.Container', "bestax's `Select` renders a single `<select>`, so a container holding more than one has no direct equivalent; emitted the plain Bulma wrapper instead");
|
|
390
|
+
return replaceWithPlain(ctx, path, element, 'div', cls, 'Select.Container');
|
|
391
|
+
}
|
|
392
|
+
// A dynamic value must not simply disappear: rename it so the condition
|
|
393
|
+
// survives, rather than dropping the modifier along with its expression.
|
|
394
|
+
for (const [from, to] of [
|
|
395
|
+
['fullwidth', 'isFullwidth'],
|
|
396
|
+
['rounded', 'isRounded'],
|
|
397
|
+
]) {
|
|
398
|
+
const attr = findAttr(element, from);
|
|
399
|
+
if (!attr)
|
|
400
|
+
continue;
|
|
401
|
+
const resolved = resolveBooleanish(attr);
|
|
402
|
+
if (resolved === 'truthy') {
|
|
403
|
+
removeAttr(element, attr);
|
|
404
|
+
addAttr(element, makeAttr(ctx.j, to));
|
|
405
|
+
}
|
|
406
|
+
else if (resolved === 'falsy') {
|
|
407
|
+
removeAttr(element, attr);
|
|
408
|
+
}
|
|
409
|
+
else {
|
|
410
|
+
attr.name = ctx.j.jsxIdentifier(to);
|
|
411
|
+
}
|
|
412
|
+
ctx.dirty = true;
|
|
413
|
+
}
|
|
414
|
+
const stateAttr = findAttr(element, 'state');
|
|
415
|
+
if (stateAttr) {
|
|
416
|
+
const literal = literalValueOf(stateAttr);
|
|
417
|
+
removeAttr(element, stateAttr);
|
|
418
|
+
if (literal.kind === 'string') {
|
|
419
|
+
const mapped = {
|
|
420
|
+
focused: 'isFocused',
|
|
421
|
+
hovered: 'isHovered',
|
|
422
|
+
loading: 'isLoading',
|
|
423
|
+
};
|
|
424
|
+
if (mapped[literal.value]) {
|
|
425
|
+
addAttr(element, makeAttr(ctx.j, mapped[literal.value]));
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
else {
|
|
429
|
+
addTodo(ctx, path, 'prop:state', 'dynamic `Select.Container` state; set the matching bestax boolean by hand');
|
|
430
|
+
}
|
|
431
|
+
ctx.dirty = true;
|
|
432
|
+
}
|
|
433
|
+
const collapsed = collapseOntoChild(ctx, path, element, 'Select.Container', 'Select');
|
|
434
|
+
if (collapsed)
|
|
435
|
+
return collapsed;
|
|
436
|
+
// rbx also lets the container wrap a NATIVE <select>. bestax's Select
|
|
437
|
+
// renders its own <select>, so keeping both nested one inside the other.
|
|
438
|
+
// Fold the native element's attributes and options up instead.
|
|
439
|
+
const kids = (element.children ?? []).filter((c) => !(c.type === 'JSXText' && c.value.trim() === ''));
|
|
440
|
+
const native = kids.length === 1 &&
|
|
441
|
+
kids[0].type === 'JSXElement' &&
|
|
442
|
+
kids[0].openingElement?.name?.type === 'JSXIdentifier' &&
|
|
443
|
+
kids[0].openingElement.name.name === 'select'
|
|
444
|
+
? kids[0]
|
|
445
|
+
: null;
|
|
446
|
+
if (native) {
|
|
447
|
+
for (const attr of attributesOf(native)) {
|
|
448
|
+
if (!findAttr(element, attr.name.name))
|
|
449
|
+
addAttr(element, attr);
|
|
450
|
+
}
|
|
451
|
+
element.children = native.children ?? [];
|
|
452
|
+
ctx.dirty = true;
|
|
453
|
+
return {
|
|
454
|
+
target: 'Select',
|
|
455
|
+
handledProps: ['fullwidth', 'rounded', 'state'],
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
return {
|
|
459
|
+
target: 'Select',
|
|
460
|
+
handledProps: ['fullwidth', 'rounded', 'state'],
|
|
461
|
+
};
|
|
462
|
+
},
|
|
463
|
+
/** rbx's Select.Option is a plain <option>. */
|
|
464
|
+
'plain-option'(ctx, path, element) {
|
|
465
|
+
return replaceWithPlain(ctx, path, element, 'option', undefined, 'Select.Option');
|
|
466
|
+
},
|
|
467
|
+
/** bestax Breadcrumb renders <ul>{children}</ul>; items are plain <li><a>. */
|
|
468
|
+
'breadcrumb-item'(ctx, path, element) {
|
|
469
|
+
const j = ctx.j;
|
|
470
|
+
const activeAttr = findAttr(element, 'active');
|
|
471
|
+
let liClass;
|
|
472
|
+
if (activeAttr) {
|
|
473
|
+
const resolved = resolveBooleanish(activeAttr);
|
|
474
|
+
if (resolved === 'truthy')
|
|
475
|
+
liClass = 'is-active';
|
|
476
|
+
else if (resolved === 'expression') {
|
|
477
|
+
addTodo(ctx, path, 'prop:active', "dynamic Breadcrumb.Item active; set the li className={active ? 'is-active' : undefined} by hand");
|
|
478
|
+
}
|
|
479
|
+
removeAttr(element, activeAttr);
|
|
480
|
+
}
|
|
481
|
+
liClass = mergeClassName(ctx, path, element, liClass, 'Breadcrumb.Item');
|
|
482
|
+
// This handler builds its <a> by hand rather than going through
|
|
483
|
+
// replaceWithPlain, so it has to layer the helper-prop strip itself —
|
|
484
|
+
// otherwise rbx's badge*/tooltip* ride onto the intrinsic <a>.
|
|
485
|
+
const anchorAttrs = stripHelperComponentProps(ctx, path, stripModifierProps(ctx, path, attributesOf(element), 'Breadcrumb.Item'), 'Breadcrumb.Item');
|
|
486
|
+
const children = element.children ?? [];
|
|
487
|
+
const anchor = plainElement(j, 'a', undefined, anchorAttrs, children);
|
|
488
|
+
path.replace(plainElement(j, 'li', liClass, [], [anchor]));
|
|
489
|
+
ctx.dirty = true;
|
|
490
|
+
return { replaced: true };
|
|
491
|
+
},
|
|
492
|
+
/**
|
|
493
|
+
* rbx's Dropdown owns both the trigger and the menu; bestax's takes a
|
|
494
|
+
* `label` and renders the trigger itself, so the shape differs enough that
|
|
495
|
+
* a mechanical rewrite would be a guess.
|
|
496
|
+
*/
|
|
497
|
+
dropdown(ctx, path, _element) {
|
|
498
|
+
addTodo(ctx, path, 'component:Dropdown', 'bestax `Dropdown` takes a `label` and renders its own trigger; move the `<Dropdown.Trigger>` content into `label` and drop the wrapper elements');
|
|
499
|
+
return {};
|
|
500
|
+
},
|
|
501
|
+
'dropdown-container'(ctx, path, _element) {
|
|
502
|
+
addTodo(ctx, path, 'component:Dropdown.Container', 'bestax `Dropdown` is the container itself; drop this wrapper');
|
|
503
|
+
return {};
|
|
504
|
+
},
|
|
505
|
+
'dropdown-content'(ctx, path, _element) {
|
|
506
|
+
addTodo(ctx, path, 'component:Dropdown.Content', 'bestax `Dropdown` renders its own menu content; drop this wrapper and keep the `<Dropdown.Item>`s');
|
|
507
|
+
return {};
|
|
508
|
+
},
|
|
509
|
+
'dropdown-menu'(ctx, path, _element) {
|
|
510
|
+
addTodo(ctx, path, 'component:Dropdown.Menu', 'bestax `Dropdown` renders its own menu; drop this wrapper and keep the `<Dropdown.Item>`s');
|
|
511
|
+
return {};
|
|
512
|
+
},
|
|
513
|
+
'dropdown-trigger'(ctx, path, _element) {
|
|
514
|
+
addTodo(ctx, path, 'component:Dropdown.Trigger', 'bestax `Dropdown` renders its own trigger; move this content into the `label` prop');
|
|
515
|
+
return {};
|
|
516
|
+
},
|
|
517
|
+
/** rbx Level.Item align=left|right → bestax Level.Left / Level.Right. */
|
|
518
|
+
'level-item'(ctx, path, element) {
|
|
519
|
+
const result = alignTarget(ctx, path, element, 'align', { left: 'Level.Left', right: 'Level.Right' }, 'Level.Item');
|
|
520
|
+
restrictAsToTargets(ctx, path, element, result.target, ['Level.Item']);
|
|
521
|
+
return { ...result, handledProps: ['align', 'as'] };
|
|
522
|
+
},
|
|
523
|
+
/**
|
|
524
|
+
* rbx Media.Item align=content|left|right → bestax Media.{Content,Left,Right}.
|
|
525
|
+
*
|
|
526
|
+
* Only `MediaLeftProps` declares `as` ('figure' | 'div'); Content and Right
|
|
527
|
+
* do not. rbx puts `as` on every component, so it has to be resolved here,
|
|
528
|
+
* against the target the align value actually selected, rather than opted
|
|
529
|
+
* into once in the mapping table.
|
|
530
|
+
*/
|
|
531
|
+
'media-item'(ctx, path, element) {
|
|
532
|
+
const result = alignTarget(ctx, path, element, 'align', {
|
|
533
|
+
content: 'Media.Content',
|
|
534
|
+
left: 'Media.Left',
|
|
535
|
+
right: 'Media.Right',
|
|
536
|
+
}, 'Media.Content');
|
|
537
|
+
restrictAsToTargets(ctx, path, element, result.target, ['Media.Left']);
|
|
538
|
+
return { ...result, handledProps: ['align', 'as'] };
|
|
539
|
+
},
|
|
540
|
+
/**
|
|
541
|
+
* The markup maps cleanly, but three rbx behaviours do not exist in bestax
|
|
542
|
+
* and none of them fail loudly: rbx portals the modal into `document.body`,
|
|
543
|
+
* closes it on Escape by default, and clips document scroll while it is
|
|
544
|
+
* open. bestax renders inline and implements none of the three. An
|
|
545
|
+
* unmodified `<Modal>` therefore migrates to something that looks right and
|
|
546
|
+
* behaves differently, so every conversion is flagged.
|
|
547
|
+
*/
|
|
548
|
+
modal(ctx, path, _element) {
|
|
549
|
+
addTodo(ctx, path, 'component:Modal', 'bestax `Modal` renders inline rather than portalling into document.body, and implements neither Escape-to-close nor scroll locking — rbx did all three by default. Re-add whichever your UI relied on');
|
|
550
|
+
return {};
|
|
551
|
+
},
|
|
552
|
+
/** rbx Modal.Container is the modal itself in bestax. */
|
|
553
|
+
'modal-container'(_ctx, _path, _element) {
|
|
554
|
+
return { target: 'Modal' };
|
|
555
|
+
},
|
|
556
|
+
/**
|
|
557
|
+
* rbx's Navbar.Item carries `dropdown`/`up`/`tab` variants that bestax
|
|
558
|
+
* splits across Navbar.Item and Navbar.Dropdown.
|
|
559
|
+
*/
|
|
560
|
+
'navbar-item'(ctx, path, element) {
|
|
561
|
+
// Resolve the target FIRST, then fall through to the cleanup loop —
|
|
562
|
+
// returning early here left `<Navbar.Item dropdown up>` as
|
|
563
|
+
// `<Navbar.Dropdown up>`, with `up` neither removed nor flagged.
|
|
564
|
+
let target;
|
|
565
|
+
const dropdownAttr = findAttr(element, 'dropdown');
|
|
566
|
+
if (dropdownAttr) {
|
|
567
|
+
const resolved = resolveBooleanish(dropdownAttr);
|
|
568
|
+
removeAttr(element, dropdownAttr);
|
|
569
|
+
ctx.dirty = true;
|
|
570
|
+
if (resolved === 'truthy') {
|
|
571
|
+
target = 'Navbar.Dropdown';
|
|
572
|
+
}
|
|
573
|
+
else if (resolved === 'expression') {
|
|
574
|
+
addTodo(ctx, path, 'prop:dropdown', 'dynamic Navbar.Item `dropdown`; pick between `<Navbar.Item>` and `<Navbar.Dropdown>` by hand');
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
// `up` and `hoverable` exist on bestax's NavbarDropdownProps, so when the
|
|
578
|
+
// dropdown target was selected they carry straight over; discarding them
|
|
579
|
+
// lost working modifiers. On a plain Navbar.Item they have no home.
|
|
580
|
+
const unsupported = target === 'Navbar.Dropdown'
|
|
581
|
+
? ['tab', 'expanded', 'managed']
|
|
582
|
+
: ['up', 'tab', 'expanded', 'hoverable', 'managed'];
|
|
583
|
+
for (const prop of unsupported) {
|
|
584
|
+
const attr = findAttr(element, prop);
|
|
585
|
+
if (!attr)
|
|
586
|
+
continue;
|
|
587
|
+
removeAttr(element, attr);
|
|
588
|
+
addTodo(ctx, path, `prop:${prop}`, `bestax \`Navbar.Item\` has no \`${prop}\` prop; restructure or add a class by hand`);
|
|
589
|
+
ctx.dirty = true;
|
|
590
|
+
}
|
|
591
|
+
restrictAsToTargets(ctx, path, element, target ?? 'Navbar.Item', [
|
|
592
|
+
'Navbar.Item',
|
|
593
|
+
]);
|
|
594
|
+
return {
|
|
595
|
+
target,
|
|
596
|
+
handledProps: [
|
|
597
|
+
'as',
|
|
598
|
+
'dropdown',
|
|
599
|
+
'up',
|
|
600
|
+
'tab',
|
|
601
|
+
'expanded',
|
|
602
|
+
'hoverable',
|
|
603
|
+
'managed',
|
|
604
|
+
],
|
|
605
|
+
};
|
|
606
|
+
},
|
|
607
|
+
/** rbx's Navbar.Container / Navbar.Item.Container have no bestax analogue. */
|
|
608
|
+
'navbar-container'(ctx, path, _element) {
|
|
609
|
+
addTodo(ctx, path, 'component:Navbar.Container', 'bestax `Navbar` renders its own container; drop this wrapper');
|
|
610
|
+
return {};
|
|
611
|
+
},
|
|
612
|
+
/**
|
|
613
|
+
* rbx's `Navbar.Dropdown` is the MENU (`div.navbar-dropdown`); bestax calls
|
|
614
|
+
* that `Navbar.DropdownMenu` and reserves `Navbar.Dropdown` for the outer
|
|
615
|
+
* `navbar-item has-dropdown` container — which is what rbx's
|
|
616
|
+
* `<Navbar.Item dropdown>` becomes. Targeting `Navbar.Dropdown` here nested
|
|
617
|
+
* two containers and emitted no menu at all. The react-bulma-components
|
|
618
|
+
* source already makes this distinction for the same reason.
|
|
619
|
+
*/
|
|
620
|
+
'navbar-dropdown'(ctx, path, element) {
|
|
621
|
+
const boxedAttr = findAttr(element, 'boxed');
|
|
622
|
+
if (boxedAttr) {
|
|
623
|
+
removeAttr(element, boxedAttr);
|
|
624
|
+
addTodo(ctx, path, 'prop:boxed', 'bestax `Navbar.DropdownMenu` has no `boxed` prop; add className="is-boxed"');
|
|
625
|
+
ctx.dirty = true;
|
|
626
|
+
}
|
|
627
|
+
const alignAttr = findAttr(element, 'align');
|
|
628
|
+
if (alignAttr) {
|
|
629
|
+
const literal = literalValueOf(alignAttr);
|
|
630
|
+
removeAttr(element, alignAttr);
|
|
631
|
+
if (literal.kind === 'string' && literal.value === 'right') {
|
|
632
|
+
addAttr(element, makeAttr(ctx.j, 'right'));
|
|
633
|
+
}
|
|
634
|
+
else if (literal.kind !== 'string') {
|
|
635
|
+
addTodo(ctx, path, 'prop:align', 'dynamic `Navbar.Dropdown` align; set the bestax `right` boolean by hand');
|
|
636
|
+
}
|
|
637
|
+
ctx.dirty = true;
|
|
638
|
+
}
|
|
639
|
+
return {
|
|
640
|
+
target: 'Navbar.DropdownMenu',
|
|
641
|
+
handledProps: ['boxed', 'align'],
|
|
642
|
+
};
|
|
643
|
+
},
|
|
644
|
+
/** rbx Navbar.Segment align=start|end → bestax Navbar.Start / Navbar.End. */
|
|
645
|
+
'navbar-segment'(ctx, path, element) {
|
|
646
|
+
return alignTarget(ctx, path, element, 'align', { start: 'Navbar.Start', end: 'Navbar.End' }, 'Navbar.Start');
|
|
647
|
+
},
|
|
648
|
+
/** rbx Pagination.Step align=next|previous → bestax Pagination.{Next,Previous}. */
|
|
649
|
+
'pagination-step'(ctx, path, element) {
|
|
650
|
+
return alignTarget(ctx, path, element, 'align', { next: 'Pagination.Next', previous: 'Pagination.Previous' }, 'Pagination.Next');
|
|
651
|
+
},
|
|
652
|
+
/**
|
|
653
|
+
* rbx's Panel.Tab is one tab inside a Panel.Tab.Group; bestax's Panel.Tabs
|
|
654
|
+
* is the group and its children are plain anchors.
|
|
655
|
+
*/
|
|
656
|
+
'panel-tab'(ctx, path, element) {
|
|
657
|
+
const activeAttr = findAttr(element, 'active');
|
|
658
|
+
let className;
|
|
659
|
+
if (activeAttr) {
|
|
660
|
+
const resolved = resolveBooleanish(activeAttr);
|
|
661
|
+
if (resolved === 'truthy')
|
|
662
|
+
className = 'is-active';
|
|
663
|
+
else if (resolved === 'expression') {
|
|
664
|
+
addTodo(ctx, path, 'prop:active', "dynamic Panel.Tab active; set className={active ? 'is-active' : undefined} by hand");
|
|
665
|
+
}
|
|
666
|
+
removeAttr(element, activeAttr);
|
|
667
|
+
}
|
|
668
|
+
return replaceWithPlain(ctx, path, element, 'a', className, 'Panel.Tab');
|
|
669
|
+
},
|
|
670
|
+
/**
|
|
671
|
+
* rbx's `Tab.Group` renders `div.tabs > ul > li`; bestax's `Tabs` renders
|
|
672
|
+
* only the `div.tabs` and leaves the `<ul>` to `Tabs.List`. Renaming
|
|
673
|
+
* straight across put the `<li>` items directly inside the div, which is
|
|
674
|
+
* not valid Bulma tabs markup. Wrap the children in a `Tabs.List`.
|
|
675
|
+
*/
|
|
676
|
+
'tab-group'(ctx, _path, element) {
|
|
677
|
+
const children = element.children ?? [];
|
|
678
|
+
const solid = children.filter((c) => !(c.type === 'JSXText' && c.value.trim() === ''));
|
|
679
|
+
// Already wrapped by hand, or nothing to wrap.
|
|
680
|
+
const alreadyList = solid.length === 1 &&
|
|
681
|
+
solid[0].type === 'JSXElement' &&
|
|
682
|
+
/(^|\.)List$/.test((jsxNameParts(solid[0].openingElement?.name) ?? []).join('.'));
|
|
683
|
+
if (solid.length === 0 || alreadyList)
|
|
684
|
+
return { target: 'Tabs' };
|
|
685
|
+
const local = ctx.reserve('Tabs');
|
|
686
|
+
const listName = `${local}.List`;
|
|
687
|
+
element.children = [
|
|
688
|
+
ctx.j.jsxElement(ctx.j.jsxOpeningElement(buildJsxName(ctx.j, listName), [], false), ctx.j.jsxClosingElement(buildJsxName(ctx.j, listName)), children),
|
|
689
|
+
];
|
|
690
|
+
ctx.dirty = true;
|
|
691
|
+
return { target: 'Tabs' };
|
|
692
|
+
},
|
|
693
|
+
/** Marks the element for the column breakpoint pass in responsive.ts. */
|
|
694
|
+
column() {
|
|
695
|
+
return {};
|
|
696
|
+
},
|
|
697
|
+
'column-group'() {
|
|
698
|
+
return {};
|
|
699
|
+
},
|
|
700
|
+
};
|
|
701
|
+
export function runSpecial(name, ctx, path, element) {
|
|
702
|
+
const handler = SPECIALS[name];
|
|
703
|
+
if (!handler)
|
|
704
|
+
return {};
|
|
705
|
+
return handler(ctx, path, element);
|
|
706
|
+
}
|