@react-x11/components 0.12.0 → 0.13.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 (80) hide show
  1. package/dist/html/controls.d.ts +12 -3
  2. package/dist/html/controls.d.ts.map +1 -1
  3. package/dist/html/controls.js +15 -55
  4. package/dist/html/controls.js.map +1 -1
  5. package/dist/html/css/cascade.d.ts +134 -20
  6. package/dist/html/css/cascade.d.ts.map +1 -1
  7. package/dist/html/css/cascade.js +519 -122
  8. package/dist/html/css/cascade.js.map +1 -1
  9. package/dist/html/css/parse.d.ts +29 -2
  10. package/dist/html/css/parse.d.ts.map +1 -1
  11. package/dist/html/css/parse.js +277 -9
  12. package/dist/html/css/parse.js.map +1 -1
  13. package/dist/html/css/style.d.ts +14 -0
  14. package/dist/html/css/style.d.ts.map +1 -1
  15. package/dist/html/css/style.js +2 -3
  16. package/dist/html/css/style.js.map +1 -1
  17. package/dist/html/css/ua.d.ts +11 -1
  18. package/dist/html/css/ua.d.ts.map +1 -1
  19. package/dist/html/css/ua.js +59 -7
  20. package/dist/html/css/ua.js.map +1 -1
  21. package/dist/html/dom.d.ts.map +1 -1
  22. package/dist/html/dom.js +3 -1
  23. package/dist/html/dom.js.map +1 -1
  24. package/dist/html/fonts.d.ts +25 -1
  25. package/dist/html/fonts.d.ts.map +1 -1
  26. package/dist/html/fonts.js +99 -6
  27. package/dist/html/fonts.js.map +1 -1
  28. package/dist/html/form.d.ts +200 -0
  29. package/dist/html/form.d.ts.map +1 -0
  30. package/dist/html/form.js +801 -0
  31. package/dist/html/form.js.map +1 -0
  32. package/dist/html/index.d.ts +12 -0
  33. package/dist/html/index.d.ts.map +1 -1
  34. package/dist/html/index.js +30 -301
  35. package/dist/html/index.js.map +1 -1
  36. package/dist/html/layout/block.d.ts.map +1 -1
  37. package/dist/html/layout/block.js +5 -1
  38. package/dist/html/layout/block.js.map +1 -1
  39. package/dist/html/layout/boxes.d.ts +31 -1
  40. package/dist/html/layout/boxes.d.ts.map +1 -1
  41. package/dist/html/layout/boxes.js +52 -8
  42. package/dist/html/layout/boxes.js.map +1 -1
  43. package/dist/html/layout/flex.js +4 -1
  44. package/dist/html/layout/flex.js.map +1 -1
  45. package/dist/html/layout/inline.d.ts.map +1 -1
  46. package/dist/html/layout/inline.js +10 -0
  47. package/dist/html/layout/inline.js.map +1 -1
  48. package/dist/html/node.d.ts +87 -15
  49. package/dist/html/node.d.ts.map +1 -1
  50. package/dist/html/node.js +510 -152
  51. package/dist/html/node.js.map +1 -1
  52. package/dist/html/paint.d.ts +22 -1
  53. package/dist/html/paint.d.ts.map +1 -1
  54. package/dist/html/paint.js +290 -20
  55. package/dist/html/paint.js.map +1 -1
  56. package/dist/html/widgets.d.ts +29 -0
  57. package/dist/html/widgets.d.ts.map +1 -0
  58. package/dist/html/widgets.js +620 -0
  59. package/dist/html/widgets.js.map +1 -0
  60. package/dist/index.d.ts +1 -1
  61. package/dist/index.d.ts.map +1 -1
  62. package/dist/index.js.map +1 -1
  63. package/package.json +2 -2
  64. package/src/html/controls.ts +29 -49
  65. package/src/html/css/cascade.ts +591 -140
  66. package/src/html/css/parse.ts +324 -9
  67. package/src/html/css/style.ts +24 -3
  68. package/src/html/css/ua.ts +62 -8
  69. package/src/html/dom.ts +3 -1
  70. package/src/html/fonts.ts +116 -6
  71. package/src/html/form.ts +962 -0
  72. package/src/html/index.ts +49 -338
  73. package/src/html/layout/block.ts +3 -0
  74. package/src/html/layout/boxes.ts +78 -11
  75. package/src/html/layout/flex.ts +4 -1
  76. package/src/html/layout/inline.ts +10 -0
  77. package/src/html/node.ts +498 -135
  78. package/src/html/paint.ts +330 -29
  79. package/src/html/widgets.ts +735 -0
  80. package/src/index.ts +1 -0
package/src/html/fonts.ts CHANGED
@@ -35,6 +35,18 @@
35
35
  // keyed by a family list, the text layouts', the metrics', ntk's, that the
36
36
  // text has to be set again.
37
37
  //
38
+ // **A face the text engine cannot set is not registered.** A face is drawn by
39
+ // the engine, and an engine can read a file it cannot draw every weight of:
40
+ // ntk cuts an instance out of a variable face for the weight and the size a
41
+ // style asks for, and fontkit, which does the cutting, could not cut one out
42
+ // of a WOFF2 — the container nearly every variable web font is served in.
43
+ // The throw came out of the first text layout in the family, and the
44
+ // document it took down was nextjs.org's blog, for Geist. So a face is asked
45
+ // for an instance once, before it is registered (`refusal`), and one the
46
+ // engine refuses is a source that did not load: the next is tried, and with
47
+ // none left the family stays out of the list and the text is set in the
48
+ // next one, as a browser sets it when it cannot use a font.
49
+ //
38
50
  // A family split by `unicode-range` is registered a name per range, since the
39
51
  // font manager picks among one name's faces by weight and slant alone, and
40
52
  // the range that holds the most of the document's characters goes first: the
@@ -46,7 +58,7 @@
46
58
  // anywhere in the app (react-x11's `loadFont` says so), and nothing is ever
47
59
  // unregistered — the font manager has no way to. A session that visits many
48
60
  // sites keeps their faces, one registration per distinct declaration.
49
- import { loadFont } from 'react-x11';
61
+ import { loadFont, openFont } from 'react-x11';
50
62
  import type { Element } from 'domhandler';
51
63
 
52
64
  import type { FontFaceRule } from './css/parse.js';
@@ -126,6 +138,8 @@ interface Registry {
126
138
  /** Faces being loaded, by `faceKey`, so a second document waits for the
127
139
  * first document's request rather than making its own. */
128
140
  pending: Map<string, Promise<boolean>>;
141
+ /** Whether a face the engine refused has been said (`warnRefused`). */
142
+ warned: boolean;
129
143
  }
130
144
 
131
145
  const REGISTRIES = new WeakMap<object, Registry>();
@@ -138,6 +152,7 @@ function registryOf(app: object): Registry {
138
152
  names: new Map(),
139
153
  ready: new Set(),
140
154
  pending: new Map(),
155
+ warned: false,
141
156
  };
142
157
  REGISTRIES.set(app, registry);
143
158
  }
@@ -493,12 +508,12 @@ export class WebFonts implements FontFamilies {
493
508
  }
494
509
  if (isPromise(answer)) {
495
510
  answer.then(
496
- (result) => after(this._register(face, result)),
511
+ (result) => after(this._register(face, result, source.url)),
497
512
  () => after(false),
498
513
  );
499
514
  return 'waiting';
500
515
  }
501
- if (this._register(face, answer)) {
516
+ if (this._register(face, answer, source.url)) {
502
517
  finish(true);
503
518
  if (sync) face.state = 'ready';
504
519
  else this._settled(face, true);
@@ -529,12 +544,24 @@ export class WebFonts implements FontFamilies {
529
544
  /** Register a face's bytes under its group's name. False when the host
530
545
  * declined, or the font manager could not read them — a `.woff2` on
531
546
  * macOS, whose CoreText reads no such container, or a file that is not a
532
- * font — and the next source is tried. */
533
- private _register(face: Face, result: ResourceResult | null): boolean {
547
+ * font — or read them and cannot set text in them (`refusal`), and the
548
+ * next source is tried. */
549
+ private _register(
550
+ face: Face,
551
+ result: ResourceResult | null,
552
+ url: string,
553
+ ): boolean {
534
554
  if (this._destroyed || !this._app || result?.kind !== 'font') return false;
535
555
  const { weight, style } = face.rule;
556
+ const app = this._app as Parameters<typeof loadFont>[0];
536
557
  try {
537
- loadFont(this._app as Parameters<typeof loadFont>[0], result.bytes, {
558
+ // `loadFont` opens the file first too, and finds this one opened
559
+ const refused = refusal(app, openFont(app, result.bytes), this._fallback);
560
+ if (refused !== null) {
561
+ warnRefused(registryOf(this._app), url, refused);
562
+ return false;
563
+ }
564
+ loadFont(app, result.bytes, {
538
565
  family: face.group.name,
539
566
  // a range is registered at the weight nearest regular in it: the
540
567
  // font manager picks among a group's faces by distance from one
@@ -560,6 +587,89 @@ export class WebFonts implements FontFamilies {
560
587
  }
561
588
  }
562
589
 
590
+ /** The slice of an opened face `refusal` reads: react-x11's `Font`. */
591
+ interface OpenedFace {
592
+ variationAxes?: Record<string, { min: number; default: number; max: number }>;
593
+ variation?(settings: Record<string, number>): unknown;
594
+ }
595
+
596
+ /** The axes a style moves without naming one: ntk sets `wght` from the
597
+ * weight and `opsz` from the size (its `docs/fonts.md`), and nothing here
598
+ * hands it a `font-variation-settings`. A face with neither is set as its
599
+ * file has it, whatever else varies in it. */
600
+ const DRIVEN_AXES = ['wght', 'opsz'];
601
+
602
+ /**
603
+ * Why the text engine cannot set text in an opened face, or null when it
604
+ * can: the face is asked for the instance a layout will ask it for, at the
605
+ * far end of each axis a style moves, and the answer is the engine's own.
606
+ * What it cost unasked was the document: ntk instantiates inside `match`,
607
+ * so the throw came out of the first layout in the family, at whatever
608
+ * weight was not the file's default.
609
+ *
610
+ * Only an engine that draws through the face is asked. CoreText and
611
+ * DirectWrite move an axis themselves, and the face react-x11 opens there
612
+ * is fontkit's, for an application to read: what it cannot do says nothing
613
+ * about what they draw.
614
+ */
615
+ export function refusal(
616
+ app: object,
617
+ font: OpenedFace,
618
+ family: string,
619
+ ): string | null {
620
+ const axes = font.variationAxes;
621
+ if (!axes || typeof font.variation !== 'function') return null;
622
+ const settings: Record<string, number> = {};
623
+ for (const tag of DRIVEN_AXES) {
624
+ const axis = axes[tag];
625
+ if (!axis) continue;
626
+ const far = axis.max !== axis.default ? axis.max : axis.min;
627
+ if (far !== axis.default) settings[tag] = far;
628
+ }
629
+ if (!Object.keys(settings).length) return null;
630
+ try {
631
+ font.variation(settings);
632
+ return null;
633
+ } catch (error) {
634
+ return instantiates(app, family)
635
+ ? String((error as Error)?.message ?? error)
636
+ : null;
637
+ }
638
+ }
639
+
640
+ /** Whether the engine's faces are cut into instances as an opened one is:
641
+ * the face it matches a family with has `variation`, as ntk's has and a
642
+ * CoreText or DirectWrite one has not. An engine that cannot say — no
643
+ * family to match — is taken to, since what is at stake is the document. */
644
+ function instantiates(app: object, family: string): boolean {
645
+ try {
646
+ const fonts = (app as { fonts?: { match?(family: string): unknown } })
647
+ .fonts;
648
+ const face = fonts?.match?.(family) as OpenedFace | null | undefined;
649
+ return !face || typeof face.variation === 'function';
650
+ } catch {
651
+ return true;
652
+ }
653
+ }
654
+
655
+ /** Said once a connection, in development: a page set in its fallback
656
+ * family looks like a font that never loaded, and this says it did. */
657
+ function warnRefused(registry: Registry, url: string, why: string): void {
658
+ if (registry.warned) return;
659
+ registry.warned = true;
660
+ const g = globalThis as {
661
+ process?: { env?: Record<string, string | undefined> };
662
+ console?: { warn(message: string): void };
663
+ };
664
+ if (g.process?.env?.NODE_ENV === 'production') return;
665
+ g.console?.warn(
666
+ `@react-x11/components: <Html> loaded the font at ${url} and the text ` +
667
+ 'engine cannot set text in it, so its family is left out and the text ' +
668
+ 'is set in the next one — as it is for every face the engine refuses. ' +
669
+ `A variable font served as WOFF2 is the usual cause.\n${why}`,
670
+ );
671
+ }
672
+
563
673
  /** A face's identity within a registered name. */
564
674
  function faceKey(name: string, rule: FontFaceRule): string {
565
675
  return `${name}\u0000${faceSignature(rule)}`;