@react-x11/components 0.12.0 → 0.14.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 (150) hide show
  1. package/dist/html/controls.d.ts +41 -5
  2. package/dist/html/controls.d.ts.map +1 -1
  3. package/dist/html/controls.js +85 -57
  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 +545 -126
  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 +47 -6
  14. package/dist/html/css/parse.d.ts.map +1 -1
  15. package/dist/html/css/parse.js +336 -30
  16. package/dist/html/css/parse.js.map +1 -1
  17. package/dist/html/css/style.d.ts +142 -15
  18. package/dist/html/css/style.d.ts.map +1 -1
  19. package/dist/html/css/style.js +563 -147
  20. package/dist/html/css/style.js.map +1 -1
  21. package/dist/html/css/transform.d.ts +85 -0
  22. package/dist/html/css/transform.d.ts.map +1 -0
  23. package/dist/html/css/transform.js +425 -0
  24. package/dist/html/css/transform.js.map +1 -0
  25. package/dist/html/css/ua.d.ts +11 -1
  26. package/dist/html/css/ua.d.ts.map +1 -1
  27. package/dist/html/css/ua.js +73 -9
  28. package/dist/html/css/ua.js.map +1 -1
  29. package/dist/html/css/values.d.ts +4 -0
  30. package/dist/html/css/values.d.ts.map +1 -1
  31. package/dist/html/css/values.js.map +1 -1
  32. package/dist/html/dom.d.ts.map +1 -1
  33. package/dist/html/dom.js +3 -1
  34. package/dist/html/dom.js.map +1 -1
  35. package/dist/html/fonts.d.ts +78 -4
  36. package/dist/html/fonts.d.ts.map +1 -1
  37. package/dist/html/fonts.js +373 -19
  38. package/dist/html/fonts.js.map +1 -1
  39. package/dist/html/form.d.ts +200 -0
  40. package/dist/html/form.d.ts.map +1 -0
  41. package/dist/html/form.js +801 -0
  42. package/dist/html/form.js.map +1 -0
  43. package/dist/html/index.d.ts +12 -0
  44. package/dist/html/index.d.ts.map +1 -1
  45. package/dist/html/index.js +30 -301
  46. package/dist/html/index.js.map +1 -1
  47. package/dist/html/layout/axes.d.ts +14 -0
  48. package/dist/html/layout/axes.d.ts.map +1 -0
  49. package/dist/html/layout/axes.js +28 -0
  50. package/dist/html/layout/axes.js.map +1 -0
  51. package/dist/html/layout/block.d.ts +93 -12
  52. package/dist/html/layout/block.d.ts.map +1 -1
  53. package/dist/html/layout/block.js +393 -39
  54. package/dist/html/layout/block.js.map +1 -1
  55. package/dist/html/layout/boxes.d.ts +90 -2
  56. package/dist/html/layout/boxes.d.ts.map +1 -1
  57. package/dist/html/layout/boxes.js +74 -10
  58. package/dist/html/layout/boxes.js.map +1 -1
  59. package/dist/html/layout/cache.d.ts +1 -1
  60. package/dist/html/layout/cache.d.ts.map +1 -1
  61. package/dist/html/layout/cache.js +6 -0
  62. package/dist/html/layout/cache.js.map +1 -1
  63. package/dist/html/layout/css-grid.d.ts.map +1 -1
  64. package/dist/html/layout/css-grid.js +90 -18
  65. package/dist/html/layout/css-grid.js.map +1 -1
  66. package/dist/html/layout/flex.d.ts.map +1 -1
  67. package/dist/html/layout/flex.js +574 -84
  68. package/dist/html/layout/flex.js.map +1 -1
  69. package/dist/html/layout/inline.d.ts +3 -0
  70. package/dist/html/layout/inline.d.ts.map +1 -1
  71. package/dist/html/layout/inline.js +154 -54
  72. package/dist/html/layout/inline.js.map +1 -1
  73. package/dist/html/layout/multicol.d.ts +42 -0
  74. package/dist/html/layout/multicol.d.ts.map +1 -0
  75. package/dist/html/layout/multicol.js +897 -0
  76. package/dist/html/layout/multicol.js.map +1 -0
  77. package/dist/html/layout/shaping.d.ts +7 -0
  78. package/dist/html/layout/shaping.d.ts.map +1 -1
  79. package/dist/html/layout/shaping.js +25 -20
  80. package/dist/html/layout/shaping.js.map +1 -1
  81. package/dist/html/layout/table.d.ts.map +1 -1
  82. package/dist/html/layout/table.js +62 -18
  83. package/dist/html/layout/table.js.map +1 -1
  84. package/dist/html/node.d.ts +90 -15
  85. package/dist/html/node.d.ts.map +1 -1
  86. package/dist/html/node.js +771 -186
  87. package/dist/html/node.js.map +1 -1
  88. package/dist/html/paint.d.ts +101 -5
  89. package/dist/html/paint.d.ts.map +1 -1
  90. package/dist/html/paint.js +1502 -160
  91. package/dist/html/paint.js.map +1 -1
  92. package/dist/html/svg.d.ts +15 -2
  93. package/dist/html/svg.d.ts.map +1 -1
  94. package/dist/html/svg.js +249 -17
  95. package/dist/html/svg.js.map +1 -1
  96. package/dist/html/widgets.d.ts +29 -0
  97. package/dist/html/widgets.d.ts.map +1 -0
  98. package/dist/html/widgets.js +700 -0
  99. package/dist/html/widgets.js.map +1 -0
  100. package/dist/html/woff2.d.ts +14 -0
  101. package/dist/html/woff2.d.ts.map +1 -0
  102. package/dist/html/woff2.js +608 -0
  103. package/dist/html/woff2.js.map +1 -0
  104. package/dist/index.d.ts +1 -1
  105. package/dist/index.d.ts.map +1 -1
  106. package/dist/index.js.map +1 -1
  107. package/dist/maps/controller.d.ts +17 -1
  108. package/dist/maps/controller.d.ts.map +1 -1
  109. package/dist/maps/controller.js +27 -7
  110. package/dist/maps/controller.js.map +1 -1
  111. package/dist/richtext/node.d.ts +8 -1
  112. package/dist/richtext/node.d.ts.map +1 -1
  113. package/dist/richtext/node.js +5 -1
  114. package/dist/richtext/node.js.map +1 -1
  115. package/dist/richtext/runs.d.ts +25 -6
  116. package/dist/richtext/runs.d.ts.map +1 -1
  117. package/dist/richtext/runs.js +240 -24
  118. package/dist/richtext/runs.js.map +1 -1
  119. package/package.json +2 -2
  120. package/src/html/controls.ts +114 -51
  121. package/src/html/css/cascade.ts +614 -140
  122. package/src/html/css/color.ts +29 -0
  123. package/src/html/css/parse.ts +388 -33
  124. package/src/html/css/style.ts +688 -150
  125. package/src/html/css/transform.ts +459 -0
  126. package/src/html/css/ua.ts +77 -10
  127. package/src/html/css/values.ts +4 -0
  128. package/src/html/dom.ts +3 -1
  129. package/src/html/fonts.ts +417 -24
  130. package/src/html/form.ts +962 -0
  131. package/src/html/index.ts +49 -338
  132. package/src/html/layout/axes.ts +62 -0
  133. package/src/html/layout/block.ts +447 -37
  134. package/src/html/layout/boxes.ts +143 -14
  135. package/src/html/layout/cache.ts +6 -0
  136. package/src/html/layout/css-grid.ts +95 -22
  137. package/src/html/layout/flex.ts +675 -78
  138. package/src/html/layout/inline.ts +159 -55
  139. package/src/html/layout/multicol.ts +977 -0
  140. package/src/html/layout/shaping.ts +27 -18
  141. package/src/html/layout/table.ts +62 -19
  142. package/src/html/node.ts +798 -172
  143. package/src/html/paint.ts +1742 -241
  144. package/src/html/svg.ts +277 -19
  145. package/src/html/widgets.ts +821 -0
  146. package/src/html/woff2.ts +612 -0
  147. package/src/index.ts +1 -0
  148. package/src/maps/controller.ts +28 -7
  149. package/src/richtext/node.ts +13 -2
  150. package/src/richtext/runs.ts +313 -35
package/src/html/svg.ts CHANGED
@@ -14,7 +14,7 @@
14
14
  // as `preserveAspectRatio` says, and clipped to it.
15
15
  import { parseDocument } from 'htmlparser2';
16
16
  import { Element, Text } from 'domhandler';
17
- import type { ChildNode } from 'domhandler';
17
+ import type { ChildNode, ParentNode } from 'domhandler';
18
18
  import * as ntk from 'react-x11/ntk';
19
19
  import { isSvgRoot } from './dom.js';
20
20
  import { inkColor, isTransparent, parseColor } from './css/values.js';
@@ -183,9 +183,18 @@ export class SvgDrawing {
183
183
  private _seen = -1;
184
184
  private _seenLast: ChildNode | null = null;
185
185
  private _seenViewport = '';
186
+ /** The `fill` and `stroke` the view's tree was last given its root's. */
187
+ private _seenPaint = '';
186
188
  /** Whether a length in the tree is a percentage of the viewport; null
187
189
  * until the tree is first read. */
188
190
  private _percent: boolean | null = null;
191
+ /** Whether a `<use>` in the tree is one `SvgView` cannot draw as it
192
+ * stands (`needsExpanding`). */
193
+ private _uses = false;
194
+ /** The document's last node when a `<use>` was last looked up and its
195
+ * element not found: a streamed document may bring it yet, and has
196
+ * when its last node is another. Undefined where none was missing. */
197
+ private _missingAt: ChildNode | null | undefined = undefined;
189
198
  private _failed = false;
190
199
  /** An SVG image's own document, rather than an element of this one. */
191
200
  private readonly _standalone: boolean;
@@ -203,7 +212,11 @@ export class SvgDrawing {
203
212
  * Draw into a rectangle — the box's content box, in device pixels —
204
213
  * clipped to it. The `viewBox`, where there is one, is fitted to it as
205
214
  * `preserveAspectRatio` says; without one a user unit is a CSS pixel,
206
- * which is `scale` device pixels. `color` is `currentColor`.
215
+ * which is `scale` device pixels. `color` is `currentColor`, and `fill`
216
+ * and `stroke` the root's own where the document's styles set them (SVG
217
+ * 2, 13.2): they are properties, which a style sheet's rule sets over
218
+ * the presentation attribute, and what is in the drawing inherits them
219
+ * from its root.
207
220
  */
208
221
  draw(
209
222
  ctx: ClipContext,
@@ -213,6 +226,8 @@ export class SvgDrawing {
213
226
  h: number,
214
227
  scale: number,
215
228
  color?: string,
229
+ fill: string | null = null,
230
+ stroke: string | null = null,
216
231
  ): void {
217
232
  if (this._failed || !(w > 0 && h > 0)) return;
218
233
  // the mock backend has no path API, and SvgView draws paths
@@ -242,9 +257,12 @@ export class SvgDrawing {
242
257
  }
243
258
  const box = viewBoxOf(root);
244
259
  // the viewport in user units, which a percentage is of
260
+ const paint =
261
+ (fill === null ? '' : `fill:${fill};`) +
262
+ (stroke === null ? '' : `stroke:${stroke};`);
245
263
  const view = box
246
- ? this._viewFor(box[2], box[3])
247
- : this._viewFor(w / scale, h / scale);
264
+ ? this._viewFor(box[2], box[3], paint)
265
+ : this._viewFor(w / scale, h / scale, paint);
248
266
  if (!view) return;
249
267
  ctx.save();
250
268
  try {
@@ -294,14 +312,32 @@ export class SvgDrawing {
294
312
  }
295
313
  }
296
314
 
297
- private _viewFor(width: number, height: number): SvgViewLike | null {
315
+ private _viewFor(
316
+ width: number,
317
+ height: number,
318
+ /** The root's `fill` and `stroke` as declarations, or none. */
319
+ paint = '',
320
+ ): SvgViewLike | null {
298
321
  const root = this._root;
299
322
  const count = root.children.length;
300
323
  const last = root.lastChild;
301
- const grown = count !== this._seen || last !== this._seenLast;
302
- if (grown || this._percent === null) this._percent = hasPercent(root);
303
- const viewport = this._percent ? `${width}x${height}` : '';
304
- if (this._view && !grown && viewport === this._seenViewport) {
324
+ // more of the document has arrived since a `<use>` found nothing
325
+ const arrived =
326
+ this._missingAt !== undefined &&
327
+ lastNode(documentOf(root)) !== this._missingAt;
328
+ const grown = count !== this._seen || last !== this._seenLast || arrived;
329
+ if (grown || this._percent === null) {
330
+ this._percent = hasPercent(root);
331
+ this._uses = needsExpanding(root);
332
+ }
333
+ const sized = this._percent || this._uses;
334
+ const viewport = sized ? `${width}x${height}` : '';
335
+ if (
336
+ this._view &&
337
+ !grown &&
338
+ viewport === this._seenViewport &&
339
+ paint === this._seenPaint
340
+ ) {
305
341
  return this._view;
306
342
  }
307
343
  const View = svgViewClass();
@@ -311,15 +347,18 @@ export class SvgDrawing {
311
347
  }
312
348
  try {
313
349
  const view = this._view ?? new View(null);
314
- view.setSvgDom(
315
- this._percent || root.name.includes(':')
316
- ? copyTree(root, this._percent ? [width, height] : null)
317
- : root,
318
- );
350
+ const missing = { any: false };
351
+ const tree =
352
+ sized || root.name.includes(':')
353
+ ? copyTree(root, sized ? [width, height] : null, missing)
354
+ : root;
355
+ view.setSvgDom(paint ? painted(tree, paint) : tree);
356
+ this._missingAt = missing.any ? lastNode(documentOf(root)) : undefined;
319
357
  this._view = view;
320
358
  this._seen = count;
321
359
  this._seenLast = last;
322
360
  this._seenViewport = viewport;
361
+ this._seenPaint = paint;
323
362
  return view;
324
363
  } catch {
325
364
  this._failed = true;
@@ -328,6 +367,21 @@ export class SvgDrawing {
328
367
  }
329
368
  }
330
369
 
370
+ /**
371
+ * A drawing's root with its `fill` and `stroke` the document's: a copy of
372
+ * the root, its children the same ones, with the declarations at the end of
373
+ * its `style` — where `SvgView` reads a root's paint, over its presentation
374
+ * attributes, for what is under it to inherit.
375
+ */
376
+ function painted(root: Element, paint: string): Element {
377
+ const style = root.attribs.style;
378
+ return new Element(
379
+ root.name,
380
+ { ...root.attribs, style: style ? `${style};${paint}` : paint },
381
+ root.children,
382
+ );
383
+ }
384
+
331
385
  /**
332
386
  * The colour an SVG image's root element sets as its background, in its
333
387
  * `style` attribute — the one place an image can say it, since
@@ -413,13 +467,154 @@ function hasPercent(root: Element): boolean {
413
467
  return walk(root);
414
468
  }
415
469
 
470
+ /** The element a `<use>` refers to by a fragment, its id: one in another
471
+ * document is none here, where nothing is fetched for a drawing. */
472
+ function useTarget(el: Element): string | null {
473
+ const href = el.attribs.href ?? el.attribs['xlink:href'] ?? '';
474
+ return href.length > 1 && href.startsWith('#') ? href.slice(1) : null;
475
+ }
476
+
477
+ /** The elements under a root by their ids, the first of each. */
478
+ function idsUnder(root: Element): Map<string, Element> {
479
+ const ids = new Map<string, Element>();
480
+ const walk = (el: Element): void => {
481
+ for (const child of el.children) {
482
+ if (child.type !== 'tag') continue;
483
+ const tag = child as Element;
484
+ const id = tag.attribs.id;
485
+ if (id && !ids.has(id)) ids.set(id, tag);
486
+ walk(tag);
487
+ }
488
+ };
489
+ walk(root);
490
+ return ids;
491
+ }
492
+
493
+ /**
494
+ * Whether a `<use>` under the root is one `SvgView` cannot draw as it
495
+ * stands: it looks a reference up among the root's own elements, and draws
496
+ * a `<symbol>` as its children where they are. So one to an element
497
+ * outside the root — an icon sprite's, a hidden `<svg>` of symbols at the
498
+ * top of the page — and one to a symbol with a `viewBox` of its own are
499
+ * drawn from a copy (`copyTree`).
500
+ */
501
+ function needsExpanding(root: Element): boolean {
502
+ let ids: Map<string, Element> | null = null;
503
+ const walk = (el: Element): boolean => {
504
+ for (const child of el.children) {
505
+ if (child.type !== 'tag') continue;
506
+ const tag = child as Element;
507
+ if (localName(tag.name) === 'use') {
508
+ const id = useTarget(tag);
509
+ if (id !== null) {
510
+ ids ??= idsUnder(root);
511
+ const target = ids.get(id);
512
+ if (!target) return true;
513
+ if (localName(target.name) === 'symbol' && viewBoxOf(target)) {
514
+ return true;
515
+ }
516
+ }
517
+ }
518
+ if (walk(tag)) return true;
519
+ }
520
+ return false;
521
+ };
522
+ return walk(root);
523
+ }
524
+
525
+ function documentOf(el: Element): ParentNode {
526
+ let top: ParentNode = el;
527
+ while (top.parent) top = top.parent;
528
+ return top;
529
+ }
530
+
531
+ /** The last node of a document, in its order: what a document that is
532
+ * still arriving has another of once more of it has. */
533
+ function lastNode(top: ParentNode): ChildNode | null {
534
+ let last: ChildNode | null = top.lastChild;
535
+ while (last && 'lastChild' in last && last.lastChild) last = last.lastChild;
536
+ return last;
537
+ }
538
+
539
+ /** The first element of a document with an id, in its order. */
540
+ function elementById(top: ParentNode, id: string): Element | null {
541
+ const stack: ChildNode[] = [...top.children].reverse();
542
+ for (let node = stack.pop(); node; node = stack.pop()) {
543
+ if (node.type !== 'tag') continue;
544
+ const el = node as Element;
545
+ if (el.attribs.id === id) return el;
546
+ for (let i = el.children.length - 1; i >= 0; i -= 1) {
547
+ stack.push(el.children[i]);
548
+ }
549
+ }
550
+ return null;
551
+ }
552
+
553
+ /** How deep a `<use>` may be of a `<use>`: one that reaches itself stops. */
554
+ const USE_DEPTH = 8;
555
+
556
+ /** A `<use>`'s `width` or `height`, in user units: a number, or a
557
+ * percentage of the viewport's; null for none, and for `auto`. */
558
+ function useLength(value: string | undefined, of: number): number | null {
559
+ if (value === undefined) return null;
560
+ const n = parseFloat(value);
561
+ if (!Number.isFinite(n) || n < 0) return null;
562
+ return value.trim().endsWith('%') ? (n / 100) * of : n;
563
+ }
564
+
565
+ /** The transform that fits a `viewBox` into a viewport at `x`, `y`, as
566
+ * `preserveAspectRatio` says (SVG 2, 8.2). */
567
+ function fitTransform(
568
+ box: [number, number, number, number],
569
+ x: number,
570
+ y: number,
571
+ width: number,
572
+ height: number,
573
+ preserve: string | undefined,
574
+ ): string {
575
+ const fit = (preserve ?? '').trim().split(/\s+/);
576
+ if (fit[0] === 'defer') fit.shift();
577
+ let sx = width / box[2];
578
+ let sy = height / box[3];
579
+ if (fit[0] !== 'none') {
580
+ const align = ALIGN.exec(fit[0] ?? '') ?? ['xMidYMid', 'Mid', 'Mid'];
581
+ const scale = fit[1] === 'slice' ? Math.max(sx, sy) : Math.min(sx, sy);
582
+ x += (width - box[2] * scale) * AT[align[1]];
583
+ y += (height - box[3] * scale) * AT[align[2]];
584
+ sx = scale;
585
+ sy = scale;
586
+ }
587
+ return `translate(${x - box[0] * sx},${y - box[1] * sy}) scale(${sx},${sy})`;
588
+ }
589
+
590
+ /** What a `<use>` says of where its element goes, and not of how it looks:
591
+ * the rest is the group's the element is drawn in. */
592
+ const USE_PLACEMENT = new Set([
593
+ 'href',
594
+ 'xlink:href',
595
+ 'x',
596
+ 'y',
597
+ 'width',
598
+ 'height',
599
+ ]);
600
+
416
601
  /**
417
602
  * The tree `SvgView` reads, where the document's own will not do: with
418
603
  * local names — `SvgView` knows `rect`, not `svg:rect`, and a prefix is
419
604
  * dropped only where it is bound to SVG — and with its percentages resolved
420
- * against a viewport of `[width, height]` user units, where one is given.
605
+ * against a viewport of `[width, height]` user units, where one is given;
606
+ * and with each `<use>` that `SvgView` cannot draw (`needsExpanding`) as a
607
+ * group of what it refers to (SVG 2, 5.5): the element, from wherever in
608
+ * the document it is, or a symbol's children, its `viewBox` fitted to the
609
+ * viewport the `<use>` gives it — its `width` and `height`, and all of the
610
+ * drawing's where it has none. `missing.any` is set where one refers to an
611
+ * element the document does not have.
421
612
  */
422
- function copyTree(root: Element, viewport: [number, number] | null): Element {
613
+ function copyTree(
614
+ root: Element,
615
+ viewport: [number, number] | null,
616
+ missing?: { any: boolean },
617
+ ): Element {
423
618
  const colon = root.name.indexOf(':');
424
619
  const prefix = colon < 0 ? null : `${root.name.slice(0, colon)}:`;
425
620
  const strip = (name: string): string =>
@@ -427,7 +622,69 @@ function copyTree(root: Element, viewport: [number, number] | null): Element {
427
622
  const diagonal = viewport
428
623
  ? Math.sqrt((viewport[0] ** 2 + viewport[1] ** 2) / 2)
429
624
  : 0;
430
- const copy = (el: Element, resolve: boolean): Element => {
625
+ let ids: Map<string, Element> | null = null;
626
+ const expand = (
627
+ use: Element,
628
+ resolve: boolean,
629
+ depth: number,
630
+ ): Element | null => {
631
+ const id = useTarget(use);
632
+ if (id === null || depth >= USE_DEPTH) return null;
633
+ ids ??= idsUnder(root);
634
+ const inside = ids.get(id);
635
+ const target = inside ?? elementById(documentOf(root), id);
636
+ if (!target) {
637
+ if (missing) missing.any = true;
638
+ return null;
639
+ }
640
+ const symbol = localName(target.name) === 'symbol';
641
+ const box = symbol ? viewBoxOf(target) : null;
642
+ // one of the root's own with no viewport to fit: `SvgView` draws it
643
+ if (inside && !box) return null;
644
+ const attribs: Record<string, string> = {};
645
+ for (const name in use.attribs) {
646
+ if (!USE_PLACEMENT.has(name)) attribs[name] = use.attribs[name];
647
+ }
648
+ const x = parseFloat(use.attribs.x ?? '') || 0;
649
+ const y = parseFloat(use.attribs.y ?? '') || 0;
650
+ let placed = '';
651
+ if (box && viewport) {
652
+ const width =
653
+ useLength(use.attribs.width, viewport[0]) ??
654
+ useLength(target.attribs.width, viewport[0]) ??
655
+ viewport[0];
656
+ const height =
657
+ useLength(use.attribs.height, viewport[1]) ??
658
+ useLength(target.attribs.height, viewport[1]) ??
659
+ viewport[1];
660
+ placed = fitTransform(
661
+ box,
662
+ x,
663
+ y,
664
+ width,
665
+ height,
666
+ svgAttr(target, 'preserveAspectRatio'),
667
+ );
668
+ } else if (x || y) placed = `translate(${x},${y})`;
669
+ const transform = `${attribs.transform ?? ''} ${placed}`.trim();
670
+ if (transform) attribs.transform = transform;
671
+ // a length in a symbol with a `viewBox` is of that, and left as it is
672
+ const within = resolve && !box;
673
+ const children: ChildNode[] = [];
674
+ if (symbol) {
675
+ for (const child of target.children) {
676
+ if (child.type === 'tag') {
677
+ children.push(copy(child as Element, within, depth + 1));
678
+ }
679
+ }
680
+ } else children.push(copy(target, within, depth + 1));
681
+ return new Element('g', attribs, children);
682
+ };
683
+ const copy = (el: Element, resolve: boolean, depth = 0): Element => {
684
+ if (localName(el.name) === 'use') {
685
+ const group = expand(el, resolve, depth);
686
+ if (group) return group;
687
+ }
431
688
  const here = resolve && !OWN_UNITS.has(localName(el.name));
432
689
  const attribs = { ...el.attribs };
433
690
  if (here && viewport && el !== root) {
@@ -444,8 +701,9 @@ function copyTree(root: Element, viewport: [number, number] | null): Element {
444
701
  }
445
702
  const children: ChildNode[] = [];
446
703
  for (const child of el.children) {
447
- if (child.type === 'tag') children.push(copy(child as Element, here));
448
- else if (child.type === 'text') children.push(new Text(child.data));
704
+ if (child.type === 'tag') {
705
+ children.push(copy(child as Element, here, depth));
706
+ } else if (child.type === 'text') children.push(new Text(child.data));
449
707
  }
450
708
  return new Element(strip(el.name), attribs, children);
451
709
  };