@react-x11/components 0.13.0 → 0.14.1

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 (134) hide show
  1. package/dist/html/controls.d.ts +29 -2
  2. package/dist/html/controls.d.ts.map +1 -1
  3. package/dist/html/controls.js +70 -2
  4. package/dist/html/controls.js.map +1 -1
  5. package/dist/html/css/cascade.d.ts +42 -1
  6. package/dist/html/css/cascade.d.ts.map +1 -1
  7. package/dist/html/css/cascade.js +336 -16
  8. package/dist/html/css/cascade.js.map +1 -1
  9. package/dist/html/css/color.d.ts +18 -0
  10. package/dist/html/css/color.d.ts.map +1 -1
  11. package/dist/html/css/color.js +27 -0
  12. package/dist/html/css/color.js.map +1 -1
  13. package/dist/html/css/parse.d.ts +18 -4
  14. package/dist/html/css/parse.d.ts.map +1 -1
  15. package/dist/html/css/parse.js +66 -28
  16. package/dist/html/css/parse.js.map +1 -1
  17. package/dist/html/css/shapes.d.ts +44 -0
  18. package/dist/html/css/shapes.d.ts.map +1 -0
  19. package/dist/html/css/shapes.js +194 -0
  20. package/dist/html/css/shapes.js.map +1 -0
  21. package/dist/html/css/style.d.ts +128 -15
  22. package/dist/html/css/style.d.ts.map +1 -1
  23. package/dist/html/css/style.js +547 -144
  24. package/dist/html/css/style.js.map +1 -1
  25. package/dist/html/css/transform.d.ts +85 -0
  26. package/dist/html/css/transform.d.ts.map +1 -0
  27. package/dist/html/css/transform.js +425 -0
  28. package/dist/html/css/transform.js.map +1 -0
  29. package/dist/html/css/ua.d.ts.map +1 -1
  30. package/dist/html/css/ua.js +14 -2
  31. package/dist/html/css/ua.js.map +1 -1
  32. package/dist/html/css/values.d.ts +4 -0
  33. package/dist/html/css/values.d.ts.map +1 -1
  34. package/dist/html/css/values.js.map +1 -1
  35. package/dist/html/fonts.d.ts +55 -5
  36. package/dist/html/fonts.d.ts.map +1 -1
  37. package/dist/html/fonts.js +284 -23
  38. package/dist/html/fonts.js.map +1 -1
  39. package/dist/html/layout/axes.d.ts +14 -0
  40. package/dist/html/layout/axes.d.ts.map +1 -0
  41. package/dist/html/layout/axes.js +28 -0
  42. package/dist/html/layout/axes.js.map +1 -0
  43. package/dist/html/layout/block.d.ts +104 -12
  44. package/dist/html/layout/block.d.ts.map +1 -1
  45. package/dist/html/layout/block.js +410 -39
  46. package/dist/html/layout/block.js.map +1 -1
  47. package/dist/html/layout/boxes.d.ts +72 -1
  48. package/dist/html/layout/boxes.d.ts.map +1 -1
  49. package/dist/html/layout/boxes.js +40 -2
  50. package/dist/html/layout/boxes.js.map +1 -1
  51. package/dist/html/layout/cache.d.ts +1 -1
  52. package/dist/html/layout/cache.d.ts.map +1 -1
  53. package/dist/html/layout/cache.js +6 -0
  54. package/dist/html/layout/cache.js.map +1 -1
  55. package/dist/html/layout/css-grid.d.ts.map +1 -1
  56. package/dist/html/layout/css-grid.js +144 -24
  57. package/dist/html/layout/css-grid.js.map +1 -1
  58. package/dist/html/layout/flex.d.ts.map +1 -1
  59. package/dist/html/layout/flex.js +585 -85
  60. package/dist/html/layout/flex.js.map +1 -1
  61. package/dist/html/layout/inline.d.ts +3 -0
  62. package/dist/html/layout/inline.d.ts.map +1 -1
  63. package/dist/html/layout/inline.js +144 -54
  64. package/dist/html/layout/inline.js.map +1 -1
  65. package/dist/html/layout/multicol.d.ts +42 -0
  66. package/dist/html/layout/multicol.d.ts.map +1 -0
  67. package/dist/html/layout/multicol.js +897 -0
  68. package/dist/html/layout/multicol.js.map +1 -0
  69. package/dist/html/layout/shaping.d.ts +7 -0
  70. package/dist/html/layout/shaping.d.ts.map +1 -1
  71. package/dist/html/layout/shaping.js +25 -20
  72. package/dist/html/layout/shaping.js.map +1 -1
  73. package/dist/html/layout/table.d.ts.map +1 -1
  74. package/dist/html/layout/table.js +97 -31
  75. package/dist/html/layout/table.js.map +1 -1
  76. package/dist/html/node.d.ts +4 -0
  77. package/dist/html/node.d.ts.map +1 -1
  78. package/dist/html/node.js +319 -47
  79. package/dist/html/node.js.map +1 -1
  80. package/dist/html/paint.d.ts +83 -5
  81. package/dist/html/paint.d.ts.map +1 -1
  82. package/dist/html/paint.js +1474 -217
  83. package/dist/html/paint.js.map +1 -1
  84. package/dist/html/svg.d.ts +39 -9
  85. package/dist/html/svg.d.ts.map +1 -1
  86. package/dist/html/svg.js +482 -37
  87. package/dist/html/svg.js.map +1 -1
  88. package/dist/html/widgets.js +87 -7
  89. package/dist/html/widgets.js.map +1 -1
  90. package/dist/html/woff2.d.ts +14 -0
  91. package/dist/html/woff2.d.ts.map +1 -0
  92. package/dist/html/woff2.js +608 -0
  93. package/dist/html/woff2.js.map +1 -0
  94. package/dist/maps/controller.d.ts +17 -1
  95. package/dist/maps/controller.d.ts.map +1 -1
  96. package/dist/maps/controller.js +27 -7
  97. package/dist/maps/controller.js.map +1 -1
  98. package/dist/richtext/node.d.ts +8 -1
  99. package/dist/richtext/node.d.ts.map +1 -1
  100. package/dist/richtext/node.js +5 -1
  101. package/dist/richtext/node.js.map +1 -1
  102. package/dist/richtext/runs.d.ts +25 -6
  103. package/dist/richtext/runs.d.ts.map +1 -1
  104. package/dist/richtext/runs.js +240 -24
  105. package/dist/richtext/runs.js.map +1 -1
  106. package/package.json +1 -1
  107. package/src/html/controls.ts +85 -2
  108. package/src/html/css/cascade.ts +366 -8
  109. package/src/html/css/color.ts +29 -0
  110. package/src/html/css/parse.ts +71 -31
  111. package/src/html/css/shapes.ts +239 -0
  112. package/src/html/css/style.ts +652 -147
  113. package/src/html/css/transform.ts +459 -0
  114. package/src/html/css/ua.ts +15 -2
  115. package/src/html/css/values.ts +4 -0
  116. package/src/html/fonts.ts +312 -29
  117. package/src/html/layout/axes.ts +62 -0
  118. package/src/html/layout/block.ts +466 -37
  119. package/src/html/layout/boxes.ts +95 -3
  120. package/src/html/layout/cache.ts +6 -0
  121. package/src/html/layout/css-grid.ts +169 -32
  122. package/src/html/layout/flex.ts +686 -79
  123. package/src/html/layout/inline.ts +149 -55
  124. package/src/html/layout/multicol.ts +977 -0
  125. package/src/html/layout/shaping.ts +27 -18
  126. package/src/html/layout/table.ts +105 -35
  127. package/src/html/node.ts +354 -48
  128. package/src/html/paint.ts +1804 -315
  129. package/src/html/svg.ts +573 -36
  130. package/src/html/widgets.ts +95 -9
  131. package/src/html/woff2.ts +612 -0
  132. package/src/maps/controller.ts +28 -7
  133. package/src/richtext/node.ts +13 -2
  134. package/src/richtext/runs.ts +313 -35
@@ -25,11 +25,11 @@ import type {} from 'react-x11/jsx-runtime';
25
25
 
26
26
  import { cancelLater, later } from '../internal/timers.js';
27
27
  import { hx } from './hx.js';
28
- import { attr, tagOf } from './dom.js';
28
+ import { attr, isElement, tagOf } from './dom.js';
29
29
  import type { Document, Element } from './dom.js';
30
30
  import type { HtmlViewNode } from './node.js';
31
31
  import type { RootLook } from './css/style.js';
32
- import { buttonLabel, optionsOf, selectedOption } from './controls.js';
32
+ import { between, buttonLabel, optionsOf, selectedOption } from './controls.js';
33
33
  import type { BareField, ControlRect } from './controls.js';
34
34
  import {
35
35
  FormState,
@@ -433,12 +433,28 @@ function renderControl(
433
433
  const readOnly = attr(el, 'readonly') !== undefined;
434
434
  // a field whose box the document draws takes its content box
435
435
  const at = rect.bare ?? rect;
436
+ const left = Math.round(at.x);
437
+ const top = Math.round(at.y);
438
+ const width = Math.round(at.width);
439
+ const height = Math.round(at.height);
440
+ // What the widget shows through: its own rectangle, or where the
441
+ // document cuts the element, what the cut leaves of it — out to where a
442
+ // focus ring reaches, which a clip around the element cuts as it cuts
443
+ // the element, and one that leaves the box whole does not.
444
+ const port = rect.clip
445
+ ? between(rect.clip, {
446
+ x: left - RING_REACH,
447
+ y: top - RING_REACH,
448
+ width: width + 2 * RING_REACH,
449
+ height: height + 2 * RING_REACH,
450
+ })
451
+ : { x: left, y: top, width, height };
436
452
  const frame: Style = {
437
453
  position: 'absolute',
438
- left: Math.round(at.x),
439
- top: Math.round(at.y),
440
- width: Math.round(at.width),
441
- height: Math.round(at.height),
454
+ left: left - port.x,
455
+ top: top - port.y,
456
+ width,
457
+ height,
442
458
  // The face and the size the box was measured in, the element's: the
443
459
  // palette's from the UA sheet, or the page's where it set its own. A
444
460
  // field inherits them, and so does a `<Button>`'s or a `<Select>`'s
@@ -452,6 +468,7 @@ function renderControl(
452
468
  ...(rect.opacity !== undefined && { opacity: rect.opacity }),
453
469
  };
454
470
  const field = rect.bare ? bareField(rect.bare) : fieldChrome(look);
471
+ const order = tabOrder(el);
455
472
  // A button or a select whose font the page set is the drawn control: a
456
473
  // native bezel sets its title at AppKit's size, whatever it is handed,
457
474
  // in a bezel only as tall as that title, where the box was measured for
@@ -474,6 +491,7 @@ function renderControl(
474
491
  widget = h(Checkbox, {
475
492
  checked: attr(el, 'checked') !== undefined,
476
493
  disabled,
494
+ ...order,
477
495
  // the element's whole box takes the press, as it does in a browser:
478
496
  // a page that sizes one over its label, invisible, means the label
479
497
  style: { width: '100%', height: '100%' },
@@ -482,6 +500,9 @@ function renderControl(
482
500
  break;
483
501
  case 'radio': {
484
502
  const value = attr(el, 'value') ?? 'on';
503
+ // No `tabindex` reaches a radio: core's `<Radio>` takes no props for
504
+ // the node it draws, as the other widgets do, and the group's box is
505
+ // not the one that takes the focus.
485
506
  widget = h(
486
507
  RadioGroup,
487
508
  {
@@ -496,6 +517,7 @@ function renderControl(
496
517
  widget = h(Button, {
497
518
  label: buttonLabel(el),
498
519
  disabled,
520
+ ...order,
499
521
  ...(ownFont && { native: false }),
500
522
  style: { width: '100%', height: '100%' },
501
523
  onPress: () => ctx.press(el),
@@ -506,7 +528,11 @@ function renderControl(
506
528
  widget = h(Select, {
507
529
  options: options.map((o) => ({ value: o.value, label: o.label })),
508
530
  value: selectedOption(el) ?? undefined,
531
+ // a `<select>` with nothing selected has no options, and shows
532
+ // none: core's "Select…" is an application's prompt, not a page's
533
+ placeholder: '',
509
534
  disabled,
535
+ ...order,
510
536
  style: rect.bare
511
537
  ? [BARE_TRIGGER, { width: '100%', height: '100%' }]
512
538
  : { width: '100%', height: '100%' },
@@ -547,6 +573,7 @@ function renderControl(
547
573
  widget = hx('textarea', {
548
574
  defaultValue: forms.value(el),
549
575
  maxLength: maxLength(el),
576
+ ...order,
550
577
  style: [field, { width: '100%', height: '100%' }],
551
578
  onChange: readOnly ? undefined : (ev) => typed(ev.value),
552
579
  });
@@ -557,6 +584,7 @@ function renderControl(
557
584
  defaultValue: forms.value(el),
558
585
  placeholder: attr(el, 'placeholder'),
559
586
  maxLength: maxLength(el),
587
+ ...order,
560
588
  // Core's word for a password field: nothing in it reaches a
561
589
  // selection, PRIMARY included.
562
590
  sensitive: type === 'password',
@@ -578,18 +606,76 @@ function renderControl(
578
606
  default:
579
607
  return null;
580
608
  }
609
+ // Two boxes, always: the one the widget shows through, and in it the
610
+ // element's. A widget the document comes to cut, or stops cutting, is
611
+ // the same widget in the same place in the tree, and keeps its focus
612
+ // and its caret.
581
613
  return hx(
582
614
  'box',
583
615
  {
584
616
  key,
585
- ref: ctx.widgets.refOf(el),
586
- style: frame,
587
617
  selectable: false,
618
+ style: {
619
+ position: 'absolute',
620
+ left: port.x,
621
+ top: port.y,
622
+ width: port.width,
623
+ height: port.height,
624
+ // cut to it, and nothing itself: a press beside the widget is the
625
+ // document's
626
+ ...(rect.clip && { overflow: 'hidden', pointerEvents: 'box-none' }),
627
+ },
588
628
  },
589
- widget,
629
+ hx(
630
+ 'box',
631
+ {
632
+ ref: ctx.widgets.refOf(el),
633
+ style: frame,
634
+ selectable: false,
635
+ // a control the page keeps from assistive technology is kept from
636
+ // it here: core leaves the node, and the widget in it, out of the
637
+ // accessibility tree
638
+ ...(ariaHidden(el) && { 'aria-hidden': true }),
639
+ },
640
+ widget,
641
+ ),
590
642
  );
591
643
  }
592
644
 
645
+ /**
646
+ * What an element's `tabindex` says of its widget's place in the Tab order
647
+ * (HTML 6.6.3): a negative one is focusable — by a press, by its label, by
648
+ * `autofocus` — and not reached by Tab, which is core's `tabIndex={-1}`
649
+ * too. Radix lays a native `<select tabindex="-1" aria-hidden="true">`
650
+ * beside the picker it draws, cut to nothing: it was a stop the eye could
651
+ * not find, and Space on it opened an empty menu.
652
+ *
653
+ * Nothing else is handed over. Zero is where a control already is, and a
654
+ * positive one would put a page's control ahead of the application's own,
655
+ * whose window it is: the order between a document and what is around it
656
+ * is not the document's to set.
657
+ */
658
+ function tabOrder(el: Element): { tabIndex: -1 } | undefined {
659
+ // the rules for parsing integers (HTML 2.3.4.1): white space, a sign,
660
+ // digits, and whatever follows them ignored
661
+ const m = /^[ \t\n\f\r]*([+-]?\d+)/.exec(attr(el, 'tabindex') ?? '');
662
+ return m && Number(m[1]) < 0 ? { tabIndex: -1 } : undefined;
663
+ }
664
+
665
+ /** Whether an element is hidden from assistive technology: `aria-hidden`
666
+ * is true on it or on an element around it (WAI-ARIA 1.2, 6.6). */
667
+ function ariaHidden(el: Element): boolean {
668
+ for (let at: Element | null = el; at;) {
669
+ if (attr(at, 'aria-hidden')?.trim().toLowerCase() === 'true') return true;
670
+ const parent: Element['parent'] = at.parent;
671
+ at = isElement(parent) ? parent : null;
672
+ }
673
+ return false;
674
+ }
675
+
676
+ /** How far past its box a widget's focus ring is drawn, and then some. */
677
+ const RING_REACH = 8;
678
+
593
679
  /** A field's `maxlength`, which the widget enforces as it is typed into. */
594
680
  function maxLength(el: Element): number | undefined {
595
681
  const raw = attr(el, 'maxlength')?.trim();