bestax-migrate 2.26.0 → 2.28.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 (42) hide show
  1. package/README.md +1 -1
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +12 -6
  4. package/dist/sources/_shared/deps-common.d.ts +2 -0
  5. package/dist/sources/_shared/deps-common.d.ts.map +1 -1
  6. package/dist/sources/_shared/deps-common.js +3 -1
  7. package/dist/sources/_shared/polymorphic.d.ts.map +1 -1
  8. package/dist/sources/_shared/polymorphic.js +16 -1
  9. package/dist/sources/bloomer/mapping.d.ts.map +1 -1
  10. package/dist/sources/bloomer/mapping.js +9 -4
  11. package/dist/sources/bloomer/specials.d.ts.map +1 -1
  12. package/dist/sources/bloomer/specials.js +30 -3
  13. package/dist/sources/bulma-classes/class-map.d.ts +41 -10
  14. package/dist/sources/bulma-classes/class-map.d.ts.map +1 -1
  15. package/dist/sources/bulma-classes/class-map.js +69 -13
  16. package/dist/sources/bulma-classes/glyph.d.ts +20 -0
  17. package/dist/sources/bulma-classes/glyph.d.ts.map +1 -1
  18. package/dist/sources/bulma-classes/glyph.js +64 -0
  19. package/dist/sources/bulma-classes/html-assets.d.ts +87 -0
  20. package/dist/sources/bulma-classes/html-assets.d.ts.map +1 -0
  21. package/dist/sources/bulma-classes/html-assets.js +246 -0
  22. package/dist/sources/bulma-classes/html-rules.d.ts +5 -0
  23. package/dist/sources/bulma-classes/html-rules.d.ts.map +1 -1
  24. package/dist/sources/bulma-classes/html-rules.js +14 -2
  25. package/dist/sources/bulma-classes/html.d.ts +12 -4
  26. package/dist/sources/bulma-classes/html.d.ts.map +1 -1
  27. package/dist/sources/bulma-classes/html.js +224 -49
  28. package/dist/sources/bulma-classes/plan.d.ts +40 -1
  29. package/dist/sources/bulma-classes/plan.d.ts.map +1 -1
  30. package/dist/sources/bulma-classes/plan.js +160 -24
  31. package/dist/sources/bulma-classes/project.d.ts +10 -2
  32. package/dist/sources/bulma-classes/project.d.ts.map +1 -1
  33. package/dist/sources/bulma-classes/project.js +17 -8
  34. package/dist/sources/bulma-classes/transform.d.ts +2 -1
  35. package/dist/sources/bulma-classes/transform.d.ts.map +1 -1
  36. package/dist/sources/bulma-classes/transform.js +231 -33
  37. package/dist/sources/rbx/mapping.d.ts +10 -0
  38. package/dist/sources/rbx/mapping.d.ts.map +1 -1
  39. package/dist/sources/rbx/mapping.js +13 -7
  40. package/dist/sources/react-bulma-components/mapping.d.ts.map +1 -1
  41. package/dist/sources/react-bulma-components/mapping.js +19 -5
  42. package/package.json +4 -3
@@ -11,10 +11,16 @@
11
11
  * What a component can't carry becomes a `TODO(bestax-migrate)` line and a
12
12
  * report entry under the `html-*` rules (`html-rules.ts`): the head, the
13
13
  * `<html>` and `<body>` attributes, scripts (dropped, since a component runs
14
- * none; their code stays in the page), and what the converter couldn't write
15
- * as it was. Document-level ones sit above the component, the rest above its
16
- * `return`, where the planner's own go. A report entry names the page and
17
- * its line, never the module's.
14
+ * none; their code stays in the page), the stylesheets and scripts it loads
15
+ * (`html-assets.ts` says what each is), and what the converter couldn't
16
+ * write as it was. Document-level ones sit above the component, the rest
17
+ * above its `return`, where the planner's own go. A report entry names the
18
+ * page and its line, never the module's.
19
+ *
20
+ * Under `--css bestax` or `--css bulma` the page's link to Bulma's stylesheet
21
+ * becomes `import 'bulma/css/bulma.min.css'` at the top of the module, which
22
+ * the transform's stylesheet pass then retargets exactly as it would in a JSX
23
+ * file, so the CLI's manifest step reads the module the same way too.
18
24
  *
19
25
  * Nothing is ever overwritten: a module already beside the page stops it
20
26
  * (`html-file:exists`), so a second run changes nothing, and the page itself
@@ -31,8 +37,9 @@ import { childrenOf, isElement, isText, parseHtml, } from '../_shared/html/parse
31
37
  import { boxOfDisplay, uaDisplay, validInlineDisplay, } from '../_shared/html/whitespace.js';
32
38
  import { flaggableRoot } from './class-map.js';
33
39
  import { bulmaDisplay, bulmaOutOfFlow, HELPER_DISPLAY } from './display.js';
40
+ import { bulmaBuildMessage, bulmaMediaMessage, bulmaStylesheetMessage, classifyAsset, extensionMessage, fontAwesomeMessage, jqueryMessage, PRIMARY_CLASS, scriptMessage, stylesheetMessage, webfontMessage, } from './html-assets.js';
34
41
  import { htmlRuleId, NAVBAR_FIXED_CLASSES, THEME_CLASSES, } from './html-rules.js';
35
- import { writesTypeScript } from './project.js';
42
+ import { inNextApp, writesTypeScript } from './project.js';
36
43
  import transform from './transform.js';
37
44
  const HTML_NS = 'http://www.w3.org/1999/xhtml';
38
45
  function attributeOf(element, name) {
@@ -203,7 +210,8 @@ const JAVASCRIPT_TYPES = /^(?:|module|(?:text|application)\/(?:x-)?(?:javascript
203
210
  const SCRIPT_PATTERNS = [
204
211
  [
205
212
  'burger',
206
- /navbar-burger/,
213
+ // Bulma 0.x's docs gave the burger a `burger` class beside its own.
214
+ /navbar-burger|['"`][^'"`\n]*\.burger\b/,
207
215
  "toggles the navbar burger: bestax's `Navbar.Burger` takes `active` and `onClick`, and `Navbar.Menu` the same `active`, so keep the open state in `useState` and pass it to both",
208
216
  ],
209
217
  [
@@ -224,30 +232,40 @@ const SCRIPT_PATTERNS = [
224
232
  [
225
233
  'theme',
226
234
  /data-theme|dataset\.theme|theme-(?:dark|light)|prefers-color-scheme/,
227
- "switches Bulma's light and dark schemes: bestax's `Theme` writes `data-theme` on `<html>` from its `colorMode` (`light`, `dark` or `system`), so keep the choice in state and pass it",
235
+ "switches Bulma's light and dark schemes: bestax's `Theme` writes `data-theme` on `<html>` from its `colorMode` (`light`, `dark` or `system`), so keep the choice in state, saved in `localStorage`, and pass it; `Theme` writes the attribute from an effect, after the first paint, so for a saved choice to paint first, keep a small inline script in the `<head>` of the app's index.html that reads it (or the OS's, with none saved) and writes `data-theme` before the app loads, and start the state from what that script wrote",
228
236
  ],
229
237
  ];
238
+ /**
239
+ * The patterns a script beside the page is looked over for. A whole file is
240
+ * more than one inline script, so only the ones that name what they drive.
241
+ */
242
+ const LOCAL_SCRIPT_PATTERNS = SCRIPT_PATTERNS.filter(([token]) => ['burger', 'carousel', 'theme'].includes(token));
243
+ /** Larger than this, a script beside the page is a bundle, not the page's. */
244
+ const LOCAL_SCRIPT_LIMIT = 256 * 1024;
230
245
  const SCRIPT_KEPT = "it was dropped, since a component doesn't run a page's scripts, and its code is still in the page";
231
- function scriptNotes(script) {
246
+ /** A `media` an import can't stand for: anything but every medium. */
247
+ function forSomeMedia(media) {
248
+ const query = media?.trim().toLowerCase() ?? '';
249
+ return query !== '' && query !== 'all';
250
+ }
251
+ function scriptNotes(script, page) {
232
252
  const location = script.sourceCodeLocation;
233
253
  const line = location?.startLine ?? null;
234
254
  const endLine = location?.endLine;
235
255
  const src = attributeOf(script, 'src');
236
- if (src !== undefined) {
237
- return [
238
- {
239
- rule: htmlRuleId('html-asset'),
240
- message: `the page loads the script \`${quote(src)}\`, and a component runs no page scripts: load it in the app if the app still needs it, or replace what it does with React code`,
241
- line,
242
- },
243
- ];
244
- }
245
256
  const type = (attributeOf(script, 'type') ?? '').trim().toLowerCase();
257
+ // A browser loads a `src` only for a script of a JavaScript type; one of
258
+ // any other type (a template, data) is the page's own text, not code.
259
+ if (src !== undefined && JAVASCRIPT_TYPES.test(type)) {
260
+ page.loads.push({ url: src, loads: 'script', line });
261
+ return [];
262
+ }
263
+ const kept = src === undefined ? 'still in the page' : `in \`${quote(src)}\``;
246
264
  if (type === 'application/ld+json') {
247
265
  return [
248
266
  {
249
267
  rule: htmlRuleId('html-script', 'json-ld'),
250
- message: `structured data for search engines (JSON-LD) was dropped with the page's scripts: render it where the app sets each page's head, as \`<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(data) }} />\`; the JSON is still in the page`,
268
+ message: `structured data for search engines (JSON-LD) was dropped with the page's scripts: render it where the app sets each page's head, as \`<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(data) }} />\`; the JSON is ${kept}`,
251
269
  line,
252
270
  endLine,
253
271
  },
@@ -257,7 +275,7 @@ function scriptNotes(script) {
257
275
  return [
258
276
  {
259
277
  rule: htmlRuleId('html-script'),
260
- message: `a \`<script type="${quote(type, 40)}">\` was dropped with the page's scripts: if the page's code reads it (a template, data), move what it holds into the component; it is still in the page`,
278
+ message: `a \`<script type="${quote(type, 40)}">\` was dropped with the page's scripts: if the page's code reads it (a template, data), move what it holds into the component; it is ${kept}`,
261
279
  line,
262
280
  endLine,
263
281
  },
@@ -283,43 +301,183 @@ function scriptNotes(script) {
283
301
  }));
284
302
  }
285
303
  /** Remove every `<script>` from the content, with a note for each. */
286
- function dropScripts(parent, notes) {
304
+ function dropScripts(parent, notes, page) {
287
305
  const nodes = childrenOf(parent);
288
306
  for (let i = 0; i < nodes.length; i++) {
289
307
  const node = nodes[i];
290
308
  if (!isElement(node))
291
309
  continue;
292
310
  if (node.tagName === 'script' && node.namespaceURI === HTML_NS) {
293
- notes.push(...scriptNotes(node));
311
+ notes.push(...scriptNotes(node, page));
294
312
  nodes.splice(i, 1);
295
313
  i--;
296
314
  }
297
315
  else if (node.tagName !== 'template') {
298
- dropScripts(node, notes);
316
+ dropScripts(node, notes, page);
299
317
  }
300
318
  }
301
319
  }
302
- /** Bulma's stylesheet, by its file name. */
303
- const BULMA_STYLESHEET = /(?:^|[/\\])bulma(?:\.min)?\.css(?:$|[?#])/i;
304
- function stylesheetNote(href, line, css) {
305
- const rule = htmlRuleId('html-asset');
306
- if (!BULMA_STYLESHEET.test(href)) {
307
- return {
308
- rule,
309
- message: `the page loads the stylesheet \`${quote(href)}\`, and a component doesn't: load it once for the app, from its entry or a \`<link>\` in its index.html`,
310
- line,
311
- };
320
+ // ---- Stylesheets and scripts it loads --------------------------------------
321
+ /** The import a page's Bulma `<link>` becomes, before `--css` retargets it. */
322
+ export const BULMA_IMPORT = 'import "bulma/css/bulma.min.css";';
323
+ /**
324
+ * Where a relative URL points, read beside the page: a file there, nothing
325
+ * there, or somewhere the page's folder can't say: any other URL, one a
326
+ * `../` takes out of the page's folder (which isn't beside it, and isn't
327
+ * the codemod's to open), and one whose path can't be read.
328
+ */
329
+ function placeOf(url, dir) {
330
+ const elsewhere = { place: 'elsewhere', target: url };
331
+ if (!isRelative(url))
332
+ return elsewhere;
333
+ const target = urlPath(url);
334
+ const file = path.resolve(dir, target);
335
+ if (path.relative(dir, file).split(/[/\\]/)[0] === '..')
336
+ return elsewhere;
337
+ let stat;
338
+ try {
339
+ stat = fs.statSync(file, { throwIfNoEntry: false });
312
340
  }
313
- const load = {
314
- keep: "load Bulma once for the app, from its entry (`import 'bulma/css/bulma.min.css'`, with `bulma` installed) or a `<link>` in its index.html",
315
- bestax: "under `--css bestax` the app loads bestax's bundle instead, once, from its entry: `import '@allxsmith/bestax-bulma/bestax.css'`. That bundle builds Bulma with bestax blue as its primary, so `is-primary` turns from Bulma's turquoise to blue; to keep the page's color, set the `--bulma-primary-*` variables, `--bulma-primary-invert-l` among them (the bestax-migrate skill's `references/css-migration.md` says which), or use `--css bulma`",
316
- bulma: "under `--css bulma` the app loads Bulma and bestax's extras once, from its entry: `import 'bulma/css/bulma.min.css'` and `import '@allxsmith/bestax-bulma/extras.css'`",
317
- }[css];
318
- return {
319
- rule,
320
- message: `the page loads Bulma's stylesheet from \`${quote(href)}\`, and a component doesn't: ${load}`,
341
+ catch {
342
+ // A folder on the way that can't be searched (`EACCES`), say.
343
+ return elsewhere;
344
+ }
345
+ return stat?.isFile()
346
+ ? { place: 'beside', target }
347
+ : { place: 'missing', target };
348
+ }
349
+ /**
350
+ * A script beside the page, looked over the way an inline script is, for
351
+ * the patterns that name what they drive. Null when it can't be read, is too
352
+ * large to be the page's own, or shows none of them.
353
+ */
354
+ function localScriptNotes(quoted, target, dir, line) {
355
+ let code;
356
+ try {
357
+ const file = path.resolve(dir, target);
358
+ if (fs.statSync(file).size > LOCAL_SCRIPT_LIMIT)
359
+ return null;
360
+ code = fs.readFileSync(file, 'utf8');
361
+ }
362
+ catch {
363
+ return null;
364
+ }
365
+ const found = LOCAL_SCRIPT_PATTERNS.filter(([, pattern]) => pattern.test(code));
366
+ if (found.length === 0)
367
+ return null;
368
+ return found.map(([token, , recipe]) => ({
369
+ rule: htmlRuleId('html-script', token),
370
+ message: `the page's script \`${quoted}\` ${recipe}; a component doesn't load it, and its code is still in \`${target}\``,
321
371
  line,
322
- };
372
+ }));
373
+ }
374
+ /** Whether the page's markup has a primary color class. */
375
+ function usesPrimary(root) {
376
+ return childrenOf(root).some(child => isElement(child) &&
377
+ (classesOf(child).some(token => PRIMARY_CLASS.test(token)) ||
378
+ usesPrimary(child)));
379
+ }
380
+ /**
381
+ * The stylesheets and scripts the page loads, as notes: one for each, but
382
+ * one for an extension's stylesheet and script together, since they are one
383
+ * thing to replace. Under `--css bestax` or `--css bulma`, Bulma's standard
384
+ * stylesheet becomes an import in the module (`importsBulma`), which the
385
+ * transform then retargets as it would the same import in a JSX file, and is
386
+ * noted only for what that leaves to do. Not for a link with a media query,
387
+ * which an import can't stand for, nor in a Next.js app, which takes a
388
+ * global stylesheet only in its root layout or `pages/_app`.
389
+ */
390
+ function assetNotes(page, root) {
391
+ const notes = [];
392
+ const shared = new Map();
393
+ const note = (rule, message, line) => notes.push({ rule, message, line });
394
+ for (const load of page.loads) {
395
+ const asset = classifyAsset(load.url, load.loads);
396
+ const quoted = quote(load.url);
397
+ if (asset.kind === 'bulma' &&
398
+ asset.build === null &&
399
+ forSomeMedia(load.media)) {
400
+ note(htmlRuleId('html-asset', 'bulma'), bulmaMediaMessage(quoted, asset.version, quote(load.media, 40)), load.line);
401
+ continue;
402
+ }
403
+ const key = asset.kind === 'bulma-extension'
404
+ ? `extension:${asset.name.toLowerCase()}`
405
+ : asset.kind === 'bulma' && asset.build === null
406
+ ? 'bulma'
407
+ : null;
408
+ if (key !== null) {
409
+ const group = shared.get(key);
410
+ if (group)
411
+ group.loads.push(load);
412
+ else
413
+ shared.set(key, { asset, loads: [load] });
414
+ continue;
415
+ }
416
+ switch (asset.kind) {
417
+ case 'bulma':
418
+ note(htmlRuleId('html-asset', 'bulma'), bulmaBuildMessage(asset.build, quoted), load.line);
419
+ break;
420
+ case 'font-awesome':
421
+ note(htmlRuleId('html-asset', 'font-awesome'), fontAwesomeMessage(asset, quoted), load.line);
422
+ break;
423
+ case 'jquery':
424
+ note(htmlRuleId('html-asset', 'jquery'), jqueryMessage(quoted), load.line);
425
+ break;
426
+ case 'webfont':
427
+ note(htmlRuleId('html-asset', 'webfont'), webfontMessage(asset, quoted), load.line);
428
+ break;
429
+ case 'stylesheet': {
430
+ const { place, target } = placeOf(load.url, page.dir);
431
+ note(htmlRuleId('html-asset', 'stylesheet'), stylesheetMessage(quoted, place, quote(target)), load.line);
432
+ break;
433
+ }
434
+ case 'script': {
435
+ const { place, target } = placeOf(load.url, page.dir);
436
+ const local = place === 'beside'
437
+ ? localScriptNotes(quoted, target, page.dir, load.line)
438
+ : null;
439
+ if (local)
440
+ notes.push(...local);
441
+ else {
442
+ note(htmlRuleId('html-asset', 'script'), scriptMessage(quoted, place, quote(target)), load.line);
443
+ }
444
+ break;
445
+ }
446
+ }
447
+ }
448
+ let importsBulma = false;
449
+ for (const { asset, loads } of shared.values()) {
450
+ const lines = [
451
+ ...new Set(loads.flatMap(load => (load.line === null ? [] : [load.line]))),
452
+ ];
453
+ const quoted = [...new Set(loads.map(load => quote(load.url)))];
454
+ let message;
455
+ let rule;
456
+ if (asset.kind === 'bulma') {
457
+ rule = htmlRuleId('html-asset', 'bulma');
458
+ importsBulma = page.css !== 'keep' && !page.nextjs;
459
+ message = bulmaStylesheetMessage({
460
+ quoted: quoted[0],
461
+ version: asset.version,
462
+ css: page.css,
463
+ usesPrimary: usesPrimary(root),
464
+ nextjs: page.nextjs,
465
+ });
466
+ }
467
+ else {
468
+ rule = htmlRuleId('html-asset', 'bulma-extension');
469
+ message = extensionMessage(asset.name, quoted, new Set(loads.map(load => load.loads)), page.css);
470
+ }
471
+ if (message === null)
472
+ continue;
473
+ notes.push({
474
+ rule,
475
+ message,
476
+ line: loads[0].line,
477
+ ...(lines.length > 1 ? { lines } : {}),
478
+ });
479
+ }
480
+ return { notes, importsBulma };
323
481
  }
324
482
  /** What a `<meta>` is for: its `name`, `property`, `http-equiv` or charset. */
325
483
  function metaKind(meta) {
@@ -336,7 +494,7 @@ function listed(values) {
336
494
  const shown = unique.slice(0, 6).join(', ');
337
495
  return unique.length > 6 ? `${shown}, …` : shown;
338
496
  }
339
- function headNotes(head, css, notes) {
497
+ function headNotes(head, notes, page) {
340
498
  const metas = [];
341
499
  const links = [];
342
500
  for (const node of head) {
@@ -359,7 +517,12 @@ function headNotes(head, css, notes) {
359
517
  const rel = (attributeOf(node, 'rel') ?? '').toLowerCase().split(/\s+/);
360
518
  const href = attributeOf(node, 'href');
361
519
  if (rel.includes('stylesheet') && href !== undefined) {
362
- notes.push(stylesheetNote(href, line, css));
520
+ page.loads.push({
521
+ url: href,
522
+ loads: 'stylesheet',
523
+ line,
524
+ media: attributeOf(node, 'media'),
525
+ });
363
526
  }
364
527
  else {
365
528
  links.push(node);
@@ -375,7 +538,7 @@ function headNotes(head, css, notes) {
375
538
  });
376
539
  break;
377
540
  case 'script':
378
- notes.push(...scriptNotes(node));
541
+ notes.push(...scriptNotes(node, page));
379
542
  break;
380
543
  case 'base':
381
544
  notes.push({
@@ -757,9 +920,11 @@ function todoLine(note, page) {
757
920
  const message = note.message.replace(/[\r\n\u2028\u2029]+/g, ' ').trim();
758
921
  const at = note.line === null
759
922
  ? ''
760
- : ` (${page}:${note.line}${note.endLine !== undefined && note.endLine > note.line
761
- ? `-${note.endLine}`
762
- : ''})`;
923
+ : note.lines !== undefined
924
+ ? ` (${page}:${note.lines.join(', ')})`
925
+ : ` (${page}:${note.line}${note.endLine !== undefined && note.endLine > note.line
926
+ ? `-${note.endLine}`
927
+ : ''})`;
763
928
  return `// TODO(bestax-migrate): ${message}${at}`;
764
929
  }
765
930
  /** What a shared line says about several relative URLs, by rule. */
@@ -882,14 +1047,22 @@ export function convertPage(file, source, collector, options = {}) {
882
1047
  }, outputPath);
883
1048
  }
884
1049
  const notes = { document: [], content: [] };
1050
+ const context = {
1051
+ dir: path.dirname(file),
1052
+ css: options.cssMode ?? 'keep',
1053
+ nextjs: inNextApp(file, options.packageLanguages),
1054
+ loads: [],
1055
+ };
885
1056
  parseNotes(parsed, notes.document);
886
- headNotes(parsed.head, options.cssMode ?? 'keep', notes.document);
1057
+ headNotes(parsed.head, notes.document, context);
887
1058
  const body = parsed.kind === 'document' ? parsed.root : null;
888
1059
  documentAttributeNotes('html', body?.parentNode && isElement(body.parentNode)
889
1060
  ? body.parentNode
890
1061
  : undefined, notes.document);
891
1062
  documentAttributeNotes('body', body ?? undefined, notes.document);
892
- dropScripts(parsed.root, notes.document);
1063
+ dropScripts(parsed.root, notes.document, context);
1064
+ const assets = assetNotes(context, parsed.root);
1065
+ notes.document.push(...assets.notes);
893
1066
  const emitted = emitJsx(parsed.root, {
894
1067
  displayOf: pageDisplayOf,
895
1068
  indent: 2,
@@ -905,6 +1078,8 @@ export function convertPage(file, source, collector, options = {}) {
905
1078
  const lines = [
906
1079
  ...header,
907
1080
  ...(header.length > 0 ? [''] : []),
1081
+ // The transform retargets it under `--css`, as it would in a JSX file.
1082
+ ...(assets.importsBulma ? [BULMA_IMPORT] : []),
908
1083
  ...notes.document.map(note => todoLine(note, page)),
909
1084
  `export default function ${componentName(file)}() {`,
910
1085
  ...contentLines(notes.content, page).map(line => ` ${line}`),
@@ -7,8 +7,13 @@
7
7
  * renders exactly the markup the element did. Anything that would change the
8
8
  * markup is either left in `className` (a class with no prop here) or refuses
9
9
  * the whole element with a TODO (an attribute or tag the component would
10
- * render differently).
10
+ * render differently). One exception, on purpose: Font Awesome's own
11
+ * `<i aria-hidden="true">` alone in a `.icon` that carries nothing assistive
12
+ * tech reads (the table's `hiddenMovesBeside`) converts to `Icon`, which writes that attribute
13
+ * on the `.icon` instead (`movesHidden`), since the glyph is hidden either
14
+ * way.
11
15
  */
16
+ import { type Glyph } from './glyph.js';
12
17
  export interface ElementFacts {
13
18
  tag: string;
14
19
  /** The static className, split on whitespace, in order. */
@@ -102,6 +107,26 @@ export interface ElementFacts {
102
107
  * The string the element's content renders, when it is one static text.
103
108
  */
104
109
  text?: string;
110
+ /**
111
+ * The classes the element directly around this one renders: its static
112
+ * ones, or for a bestax component already in the file, its root class and
113
+ * the flags written out as `true`, so a re-run reads the same. What an
114
+ * entry that `readsGlyph` reads to point at `Control`'s icon props.
115
+ */
116
+ parentTokens?: readonly string[];
117
+ /**
118
+ * The nearest <a> or <button> around this element, a bestax component that
119
+ * renders one included, and what names it: its own `aria-label` or
120
+ * `aria-labelledby`, written out, text in it outside every icon, or `none`
121
+ * when nothing does. `label` is left out when it can't be told from here (a
122
+ * name or content written as an expression, or a component the codemod
123
+ * doesn't know inside). What an entry that `readsGlyph` reads to say
124
+ * whether an icon is decoration there.
125
+ */
126
+ clickableAround?: {
127
+ tag: string;
128
+ label?: 'aria-label' | 'aria-labelledby' | 'text' | 'none';
129
+ };
105
130
  }
106
131
  export interface ChildFacts {
107
132
  tag: string;
@@ -178,6 +203,20 @@ export interface Conversion {
178
203
  * they go, and the element closes itself.
179
204
  */
180
205
  rendersChildren?: true;
206
+ /**
207
+ * The glyph the target renders from props in place of the element's only
208
+ * child (`Icon`'s `<i>`), written before the props above. The child goes,
209
+ * as `rendersChildren`, which is set beside it, says.
210
+ */
211
+ glyph?: Glyph;
212
+ /**
213
+ * That child carried the `aria-hidden` the target writes on the element
214
+ * (Font Awesome's own `<i aria-hidden="true">`), so it moves up: the one
215
+ * way a conversion's markup differs from the element's, on purpose, since
216
+ * the glyph is hidden either way. The render tests compare it with the
217
+ * attribute moved up, and nothing else.
218
+ */
219
+ movesHidden?: true;
181
220
  /**
182
221
  * The target renders the bare element around this one too: the component
183
222
  * takes that element's place, with its `key`.
@@ -1 +1 @@
1
- {"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../../../src/sources/bulma-classes/plan.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../../../src/sources/bulma-classes/plan.ts"],"names":[],"mappings":"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"}