@lightningtv/solid 3.1.7 → 3.1.8

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 (48) hide show
  1. package/dist/src/core/animation.d.ts +1 -1
  2. package/dist/src/core/animation.js.map +1 -1
  3. package/dist/src/core/config.d.ts +2 -2
  4. package/dist/src/core/config.js.map +1 -1
  5. package/dist/src/core/{domRenderer.d.ts → dom-renderer/domRenderer.d.ts} +30 -7
  6. package/dist/src/core/{domRenderer.js → dom-renderer/domRenderer.js} +633 -122
  7. package/dist/src/core/dom-renderer/domRenderer.js.map +1 -0
  8. package/dist/src/core/dom-renderer/domRendererTypes.d.ts +111 -0
  9. package/dist/src/core/dom-renderer/domRendererTypes.js +2 -0
  10. package/dist/src/core/dom-renderer/domRendererTypes.js.map +1 -0
  11. package/dist/src/core/dom-renderer/domRendererUtils.d.ts +23 -0
  12. package/dist/src/core/dom-renderer/domRendererUtils.js +231 -0
  13. package/dist/src/core/dom-renderer/domRendererUtils.js.map +1 -0
  14. package/dist/src/core/elementNode.d.ts +8 -8
  15. package/dist/src/core/elementNode.js +54 -15
  16. package/dist/src/core/elementNode.js.map +1 -1
  17. package/dist/src/core/index.d.ts +4 -2
  18. package/dist/src/core/index.js +1 -2
  19. package/dist/src/core/index.js.map +1 -1
  20. package/dist/src/core/intrinsicTypes.d.ts +16 -6
  21. package/dist/src/core/lightningInit.d.ts +7 -89
  22. package/dist/src/core/lightningInit.js +13 -5
  23. package/dist/src/core/lightningInit.js.map +1 -1
  24. package/dist/src/core/shaders.d.ts +12 -11
  25. package/dist/src/core/shaders.js +0 -90
  26. package/dist/src/core/shaders.js.map +1 -1
  27. package/dist/src/primitives/Grid.jsx +2 -2
  28. package/dist/src/primitives/Grid.jsx.map +1 -1
  29. package/dist/src/primitives/Image.jsx +18 -0
  30. package/dist/src/primitives/Image.jsx.map +1 -1
  31. package/dist/src/render.d.ts +3 -3
  32. package/dist/src/render.js.map +1 -1
  33. package/dist/tsconfig.tsbuildinfo +1 -1
  34. package/package.json +2 -2
  35. package/src/core/animation.ts +6 -3
  36. package/src/core/config.ts +5 -2
  37. package/src/core/{domRenderer.ts → dom-renderer/domRenderer.ts} +738 -164
  38. package/src/core/dom-renderer/domRendererTypes.ts +150 -0
  39. package/src/core/dom-renderer/domRendererUtils.ts +291 -0
  40. package/src/core/elementNode.ts +98 -35
  41. package/src/core/index.ts +4 -2
  42. package/src/core/intrinsicTypes.ts +22 -6
  43. package/src/core/lightningInit.ts +20 -124
  44. package/src/core/shaders.ts +17 -110
  45. package/src/primitives/Grid.tsx +8 -7
  46. package/src/primitives/Image.tsx +20 -0
  47. package/src/render.ts +2 -1
  48. package/dist/src/core/domRenderer.js.map +0 -1
@@ -5,41 +5,16 @@ Experimental DOM renderer
5
5
  */
6
6
  import * as lng from '@lightningjs/renderer';
7
7
  import { EventEmitter } from '@lightningjs/renderer/utils';
8
- import { Config } from './config.js';
9
- import { isFunc } from './utils.js';
10
- const colorToRgba = (c) => `rgba(${(c >> 24) & 0xff},${(c >> 16) & 0xff},${(c >> 8) & 0xff},${(c & 0xff) / 255})`;
11
- function applyEasing(easing, progress) {
12
- if (isFunc(easing)) {
13
- return easing(progress);
14
- }
15
- switch (easing) {
16
- case 'linear':
17
- default:
18
- return progress;
19
- case 'ease-in':
20
- return progress * progress;
21
- case 'ease-out':
22
- return progress * (2 - progress);
23
- case 'ease-in-out':
24
- return progress < 0.5
25
- ? 2 * progress * progress
26
- : -1 + (4 - 2 * progress) * progress;
27
- }
28
- }
29
- function interpolate(start, end, t) {
30
- return start + (end - start) * t;
31
- }
32
- function interpolateColor(start, end, t) {
33
- return ((interpolate((start >> 24) & 0xff, (end >> 24) & 0xff, t) << 24) |
34
- (interpolate((start >> 16) & 0xff, (end >> 16) & 0xff, t) << 16) |
35
- (interpolate((start >> 8) & 0xff, (end >> 8) & 0xff, t) << 8) |
36
- interpolate(start & 0xff, end & 0xff, t));
37
- }
38
- function interpolateProp(name, start, end, t) {
39
- return name.startsWith('color')
40
- ? interpolateColor(start, end, t)
41
- : interpolate(start, end, t);
42
- }
8
+ import { Config } from '../config.js';
9
+ import { colorToRgba, buildGradientStops, computeLegacyObjectFit, applySubTextureScaling, getNodeLineHeight, applyEasing, interpolateProp, isRenderStateInBounds, nodeHasTextureSource, computeRenderStateForNode, compactString, } from './domRendererUtils.js';
10
+ // Feature detection for legacy brousers
11
+ const _styleRef = typeof document !== 'undefined' ? document.documentElement?.style || {} : {};
12
+ const supportsObjectFit = 'objectFit' in _styleRef;
13
+ const supportsObjectPosition = 'objectPosition' in _styleRef;
14
+ const supportsMixBlendMode = 'mixBlendMode' in _styleRef;
15
+ const supportsStandardMask = 'maskImage' in _styleRef;
16
+ const supportsWebkitMask = 'webkitMaskImage' in _styleRef;
17
+ const supportsCssMask = supportsStandardMask || supportsWebkitMask;
43
18
  /*
44
19
  Animations
45
20
  */
@@ -78,6 +53,8 @@ function updateAnimations(time) {
78
53
  // Animation complete
79
54
  else {
80
55
  Object.assign(task.node.props, task.propsEnd);
56
+ task.node.boundsDirty = true;
57
+ task.node.markChildrenBoundsDirty();
81
58
  updateNodeStyles(task.node);
82
59
  task.stop();
83
60
  i--;
@@ -190,13 +167,11 @@ function animate(props, settings) {
190
167
  */
191
168
  let elMap = new WeakMap();
192
169
  function updateNodeParent(node) {
193
- if (node.parent != null) {
194
- elMap.get(node.parent).appendChild(node.div);
170
+ const parent = node.props.parent;
171
+ if (parent instanceof DOMNode) {
172
+ elMap.get(parent).appendChild(node.div);
195
173
  }
196
174
  }
197
- function getNodeLineHeight(props) {
198
- return (props.lineHeight ?? Config.fontSettings.lineHeight ?? 1.2 * props.fontSize);
199
- }
200
175
  function updateNodeStyles(node) {
201
176
  let { props } = node;
202
177
  let style = `position: absolute; z-index: ${props.zIndex};`;
@@ -252,6 +227,9 @@ function updateNodeStyles(node) {
252
227
  if (textProps.fontWeight !== 'normal') {
253
228
  style += `font-weight: ${textProps.fontWeight};`;
254
229
  }
230
+ if (textProps.fontStretch && textProps.fontStretch !== 'normal') {
231
+ style += `font-stretch: ${textProps.fontStretch};`;
232
+ }
255
233
  if (textProps.lineHeight != null) {
256
234
  style += `line-height: ${textProps.lineHeight}px;`;
257
235
  }
@@ -264,20 +242,41 @@ function updateNodeStyles(node) {
264
242
  let maxLines = textProps.maxLines || Infinity;
265
243
  switch (textProps.contain) {
266
244
  case 'width':
267
- style += `width: ${props.w}px; overflow: hidden;`;
245
+ if (textProps.maxWidth && textProps.maxWidth > 0) {
246
+ style += `width: ${textProps.maxWidth}px;`;
247
+ }
248
+ else {
249
+ style += `width: 100%;`;
250
+ }
251
+ style += `overflow: hidden;`;
268
252
  break;
269
253
  case 'both': {
270
254
  let lineHeight = getNodeLineHeight(textProps);
271
- maxLines = Math.min(maxLines, Math.floor(props.h / lineHeight));
272
- maxLines = Math.max(1, maxLines);
273
- let height = maxLines * lineHeight;
274
- style += `width: ${props.w}px; height: ${height}px; overflow: hidden;`;
255
+ const widthConstraint = textProps.maxWidth && textProps.maxWidth > 0
256
+ ? `${textProps.maxWidth}px`
257
+ : `100%`;
258
+ const heightConstraint = textProps.maxHeight && textProps.maxHeight > 0
259
+ ? textProps.maxHeight
260
+ : props.h;
261
+ let height = heightConstraint || 0;
262
+ if (height > 0) {
263
+ const maxLinesByHeight = Math.max(1, Math.floor(height / lineHeight));
264
+ maxLines = Math.min(maxLines, maxLinesByHeight);
265
+ height = Math.max(lineHeight, maxLines * lineHeight);
266
+ }
267
+ else {
268
+ maxLines = Number.isFinite(maxLines) ? Math.max(1, maxLines) : 1;
269
+ height = maxLines * lineHeight;
270
+ }
271
+ style += `width: ${widthConstraint}; height: ${height}px; overflow: hidden;`;
275
272
  break;
276
273
  }
277
274
  case 'none':
275
+ style += `width: -webkit-max-content;`;
278
276
  style += `width: max-content;`;
279
277
  break;
280
278
  }
279
+ style += `white-space: pre-wrap;`;
281
280
  if (maxLines !== Infinity) {
282
281
  // https://stackoverflow.com/a/13924997
283
282
  style += `display: -webkit-box;
@@ -288,12 +287,11 @@ function updateNodeStyles(node) {
288
287
  }
289
288
  // if (node.overflowSuffix) style += `overflow-suffix: ${node.overflowSuffix};`
290
289
  // if (node.verticalAlign) style += `vertical-align: ${node.verticalAlign};`
291
- scheduleUpdateDOMTextMeasurement(node);
292
290
  }
293
291
  // <Node>
294
292
  else {
295
293
  if (props.w !== 0)
296
- style += `width: ${props.w}px;`;
294
+ style += `width: ${props.w < 0 ? 0 : props.w}px;`;
297
295
  if (props.h !== 0)
298
296
  style += `height: ${props.h}px;`;
299
297
  let vGradient = props.colorBottom !== props.colorTop
@@ -307,53 +305,93 @@ function updateNodeStyles(node) {
307
305
  : vGradient || hGradient;
308
306
  let srcImg = null;
309
307
  let srcPos = null;
308
+ let rawImgSrc = null;
310
309
  if (props.texture != null &&
311
310
  props.texture.type === lng.TextureType.subTexture) {
312
- srcPos = props.texture.props;
313
- srcImg = `url(${props.texture.props.texture.props.src})`;
311
+ const texture = props.texture;
312
+ srcPos = texture.props;
313
+ rawImgSrc = texture.props.texture.props.src;
314
314
  }
315
315
  else if (props.src) {
316
- srcImg = `url(${props.src})`;
316
+ rawImgSrc = props.src;
317
+ }
318
+ if (rawImgSrc) {
319
+ srcImg = `url(${rawImgSrc})`;
317
320
  }
318
321
  let bgStyle = '';
319
322
  let borderStyle = '';
320
323
  let radiusStyle = '';
321
324
  let maskStyle = '';
322
- if (srcImg) {
323
- if (props.color !== 0xffffffff && props.color !== 0x00000000) {
324
- // use image as a mask
325
- bgStyle += `background-color: ${colorToRgba(props.color)}; background-blend-mode: multiply;`;
326
- maskStyle += `mask-image: ${srcImg};`;
327
- if (srcPos !== null) {
328
- maskStyle += `mask-position: -${srcPos.x}px -${srcPos.y}px;`;
329
- }
330
- else {
331
- maskStyle += `mask-size: 100% 100%;`;
325
+ let needsBackgroundLayer = false;
326
+ let imgStyle = '';
327
+ let hasDivBgTint = false;
328
+ if (rawImgSrc) {
329
+ needsBackgroundLayer = true;
330
+ const hasTint = props.color !== 0xffffffff && props.color !== 0x00000000;
331
+ if (hasTint) {
332
+ bgStyle += `background-color: ${colorToRgba(props.color)};`;
333
+ if (srcImg) {
334
+ maskStyle += `mask-image: ${srcImg};`;
335
+ if (srcPos !== null) {
336
+ maskStyle += `mask-position: -${srcPos.x}px -${srcPos.y}px;`;
337
+ }
338
+ else {
339
+ maskStyle += `mask-size: 100% 100%;`;
340
+ }
341
+ hasDivBgTint = true;
332
342
  }
333
343
  }
334
344
  else if (gradient) {
335
- // use gradient as a mask
345
+ // use gradient as a mask when no tint is applied
336
346
  maskStyle += `mask-image: ${gradient};`;
337
347
  }
338
- bgStyle += `background-image: ${srcImg};`;
339
- bgStyle += `background-repeat: no-repeat;`;
348
+ const imgStyleParts = [
349
+ 'position: absolute',
350
+ 'top: 0',
351
+ 'left: 0',
352
+ 'right: 0',
353
+ 'bottom: 0',
354
+ 'display: block',
355
+ 'pointer-events: none',
356
+ ];
340
357
  if (props.textureOptions.resizeMode?.type) {
341
- bgStyle += `background-size: ${props.textureOptions.resizeMode.type}; background-position: center;`;
358
+ const resizeMode = props.textureOptions.resizeMode;
359
+ imgStyleParts.push('width: 100%');
360
+ imgStyleParts.push('height: 100%');
361
+ imgStyleParts.push(`object-fit: ${resizeMode.type}`);
362
+ // Handle clipX and clipY for object-position
363
+ const clipX = resizeMode.clipX ?? 0.5;
364
+ const clipY = resizeMode.clipY ?? 0.5;
365
+ imgStyleParts.push(`object-position: ${clipX * 100}% ${clipY * 100}%`);
342
366
  }
343
367
  else if (srcPos !== null) {
344
- bgStyle += `background-position: -${srcPos.x}px -${srcPos.y}px;`;
368
+ imgStyleParts.push('width: auto');
369
+ imgStyleParts.push('height: auto');
370
+ imgStyleParts.push('object-fit: none');
371
+ imgStyleParts.push(`object-position: -${srcPos.x}px -${srcPos.y}px`);
345
372
  }
346
- else {
347
- bgStyle += 'background-size: 100% 100%;';
373
+ else if (props.w && !props.h) {
374
+ imgStyleParts.push('width: 100%');
375
+ imgStyleParts.push('height: auto');
348
376
  }
349
- if (maskStyle !== '') {
350
- bgStyle += maskStyle;
377
+ else if (props.h && !props.w) {
378
+ imgStyleParts.push('width: auto');
379
+ imgStyleParts.push('height: 100%');
351
380
  }
352
- // separate layers are needed for the mask
353
- if (maskStyle !== '' && node.divBg == null) {
354
- node.div.appendChild((node.divBg = document.createElement('div')));
355
- node.div.appendChild((node.divBorder = document.createElement('div')));
381
+ else {
382
+ imgStyleParts.push('width: 100%');
383
+ imgStyleParts.push('height: 100%');
384
+ imgStyleParts.push('object-fit: fill');
385
+ }
386
+ if (hasTint) {
387
+ if (supportsMixBlendMode) {
388
+ imgStyleParts.push('mix-blend-mode: multiply');
389
+ }
390
+ else {
391
+ imgStyleParts.push('opacity: 0.9');
392
+ }
356
393
  }
394
+ imgStyle = imgStyleParts.join('; ') + ';';
357
395
  }
358
396
  else if (gradient) {
359
397
  bgStyle += `background-image: ${gradient};`;
@@ -364,20 +402,21 @@ function updateNodeStyles(node) {
364
402
  bgStyle += `background-color: ${colorToRgba(props.color)};`;
365
403
  }
366
404
  if (props.shader?.props != null) {
367
- let shader = props.shader.props;
368
- let borderWidth = shader['border-w'];
369
- let borderColor = shader['border-color'];
370
- let borderGap = shader['border-gap'] ?? 0;
371
- let borderInset = shader['border-inset'] ?? true;
372
- let radius = shader['radius'];
405
+ let shaderProps = props.shader.props;
406
+ let borderWidth = shaderProps['border-w'];
407
+ let borderColor = shaderProps['border-color'];
408
+ let borderGap = shaderProps['border-gap'] ?? 0;
409
+ let borderInset = shaderProps['border-inset'] ?? true;
410
+ let radius = shaderProps['radius'];
373
411
  // Border
374
412
  if (typeof borderWidth === 'number' &&
375
413
  borderWidth !== 0 &&
376
414
  typeof borderColor === 'number' &&
377
415
  borderColor !== 0) {
416
+ const rgbaColor = colorToRgba(borderColor);
378
417
  // Handle inset borders by making gap negative
379
418
  let gap = borderInset ? -(borderWidth + borderGap) : borderGap;
380
- borderStyle += `outline: ${borderWidth}px solid ${colorToRgba(borderColor)};`;
419
+ borderStyle += `outline: ${borderWidth}px solid ${rgbaColor};`;
381
420
  borderStyle += `outline-offset: ${gap}px;`;
382
421
  }
383
422
  // Rounded
@@ -387,42 +426,254 @@ function updateNodeStyles(node) {
387
426
  else if (Array.isArray(radius) && radius.length === 4) {
388
427
  radiusStyle += `border-radius: ${radius[0]}px ${radius[1]}px ${radius[2]}px ${radius[3]}px;`;
389
428
  }
429
+ if ('radial' in shaderProps) {
430
+ const rg = shaderProps.radial;
431
+ const colors = Array.isArray(rg?.colors) ? rg.colors : [];
432
+ const stops = Array.isArray(rg?.stops) ? rg.stops : undefined;
433
+ const pivot = Array.isArray(rg?.pivot) ? rg.pivot : [0.5, 0.5];
434
+ const width = typeof rg?.w === 'number' ? rg.w : props.w || 0;
435
+ const height = typeof rg?.h === 'number' ? rg.h : width;
436
+ if (colors.length > 0) {
437
+ const gradientStops = buildGradientStops(colors, stops);
438
+ if (gradientStops) {
439
+ if (colors.length === 1) {
440
+ // Single color -> solid fill
441
+ if (srcImg || gradient) {
442
+ maskStyle += `mask-image: linear-gradient(${gradientStops});`;
443
+ }
444
+ else {
445
+ bgStyle += `background-color: ${colorToRgba(colors[0])};`;
446
+ }
447
+ }
448
+ else {
449
+ const isEllipse = width > 0 && height > 0 && width !== height;
450
+ const pivotX = (pivot[0] ?? 0.5) * 100;
451
+ const pivotY = (pivot[1] ?? 0.5) * 100;
452
+ let sizePart = '';
453
+ if (width > 0 && height > 0) {
454
+ if (!isEllipse && width === height) {
455
+ sizePart = `${Math.round(width)}px`;
456
+ }
457
+ else {
458
+ sizePart = `${Math.round(width)}px ${Math.round(height)}px`;
459
+ }
460
+ }
461
+ else {
462
+ sizePart = 'closest-side';
463
+ }
464
+ const radialGradient = `radial-gradient(${isEllipse ? 'ellipse' : 'circle'} ${sizePart} at ${pivotX.toFixed(2)}% ${pivotY.toFixed(2)}%, ${gradientStops})`;
465
+ if (srcImg || gradient) {
466
+ maskStyle += `mask-image: ${radialGradient};`;
467
+ }
468
+ else {
469
+ bgStyle += `background-image: ${radialGradient};`;
470
+ bgStyle += `background-repeat: no-repeat;`;
471
+ bgStyle += `background-size: 100% 100%;`;
472
+ }
473
+ }
474
+ }
475
+ }
476
+ }
477
+ if ('linear' in shaderProps) {
478
+ const lg = shaderProps.linear;
479
+ const colors = Array.isArray(lg?.colors) ? lg.colors : [];
480
+ const stops = Array.isArray(lg?.stops) ? lg.stops : undefined;
481
+ const angleRad = typeof lg?.angle === 'number' ? lg.angle : 0; // radians
482
+ if (colors.length > 0) {
483
+ const gradientStops = buildGradientStops(colors, stops);
484
+ if (gradientStops) {
485
+ if (colors.length === 1) {
486
+ if (srcImg || gradient) {
487
+ maskStyle += `mask-image: linear-gradient(${gradientStops});`;
488
+ }
489
+ else {
490
+ bgStyle += `background-color: ${colorToRgba(colors[0])};`;
491
+ }
492
+ }
493
+ else {
494
+ const angleDeg = 180 * (angleRad / Math.PI - 1);
495
+ const linearGradient = `linear-gradient(${angleDeg.toFixed(2)}deg, ${gradientStops})`;
496
+ if (srcImg || gradient) {
497
+ maskStyle += `mask-image: ${linearGradient};`;
498
+ }
499
+ else {
500
+ bgStyle += `background-image: ${linearGradient};`;
501
+ bgStyle += `background-repeat: no-repeat;`;
502
+ bgStyle += `background-size: 100% 100%;`;
503
+ }
504
+ }
505
+ }
506
+ }
507
+ }
508
+ }
509
+ if (maskStyle !== '') {
510
+ if (!supportsStandardMask && supportsWebkitMask) {
511
+ maskStyle = maskStyle.replace(/mask-/g, '-webkit-mask-');
512
+ }
513
+ else if (!supportsCssMask) {
514
+ maskStyle = '';
515
+ }
516
+ if (maskStyle !== '') {
517
+ needsBackgroundLayer = true;
518
+ }
390
519
  }
391
520
  style += radiusStyle;
392
- bgStyle += radiusStyle;
393
- borderStyle += radiusStyle;
394
- if (node.divBg == null) {
395
- style += bgStyle;
521
+ if (needsBackgroundLayer) {
522
+ if (node.divBg == null) {
523
+ node.divBg = document.createElement('div');
524
+ node.div.insertBefore(node.divBg, node.div.firstChild);
525
+ }
526
+ else if (node.divBg.parentElement !== node.div) {
527
+ node.div.insertBefore(node.divBg, node.div.firstChild);
528
+ }
529
+ let bgLayerStyle = 'position: absolute; top:0; left:0; right:0; bottom:0; z-index: -1; pointer-events: none; overflow: hidden;';
530
+ if (bgStyle) {
531
+ bgLayerStyle += bgStyle;
532
+ }
533
+ if (maskStyle) {
534
+ bgLayerStyle += maskStyle;
535
+ }
536
+ node.divBg.setAttribute('style', bgLayerStyle + radiusStyle);
537
+ if (rawImgSrc) {
538
+ if (!node.imgEl) {
539
+ node.imgEl = document.createElement('img');
540
+ node.imgEl.alt = '';
541
+ node.imgEl.crossOrigin = 'anonymous';
542
+ node.imgEl.setAttribute('aria-hidden', 'true');
543
+ node.imgEl.setAttribute('loading', 'lazy');
544
+ node.imgEl.removeAttribute('src');
545
+ node.imgEl.addEventListener('load', () => {
546
+ const payload = {
547
+ type: 'texture',
548
+ dimensions: {
549
+ w: node.imgEl.naturalWidth,
550
+ h: node.imgEl.naturalHeight,
551
+ },
552
+ };
553
+ node.imgEl.style.display = '';
554
+ applySubTextureScaling(node, node.imgEl, node.lazyImageSubTextureProps);
555
+ const resizeMode = node.props.textureOptions?.resizeMode;
556
+ const clipX = resizeMode?.clipX ?? 0.5;
557
+ const clipY = resizeMode?.clipY ?? 0.5;
558
+ computeLegacyObjectFit(node, node.imgEl, resizeMode, clipX, clipY, node.lazyImageSubTextureProps, supportsObjectFit, supportsObjectPosition);
559
+ node.emit('loaded', payload);
560
+ });
561
+ node.imgEl.addEventListener('error', () => {
562
+ if (node.imgEl) {
563
+ node.imgEl.removeAttribute('src');
564
+ node.imgEl.style.display = 'none';
565
+ node.imgEl.removeAttribute('data-rawSrc');
566
+ }
567
+ const failedSrc = node.imgEl?.dataset.pendingSrc || node.lazyImagePendingSrc || '';
568
+ const payload = {
569
+ type: 'texture',
570
+ error: new Error(`Failed to load image: ${failedSrc}`),
571
+ };
572
+ node.emit('failed', payload);
573
+ });
574
+ }
575
+ node.lazyImagePendingSrc = rawImgSrc;
576
+ node.lazyImageSubTextureProps = srcPos;
577
+ node.imgEl.dataset.pendingSrc = rawImgSrc;
578
+ if (node.imgEl.parentElement !== node.divBg) {
579
+ node.divBg.appendChild(node.imgEl);
580
+ }
581
+ node.imgEl.setAttribute('style', imgStyle);
582
+ if (hasDivBgTint) {
583
+ node.imgEl.style.visibility = 'hidden';
584
+ }
585
+ if (isRenderStateInBounds(node.renderState)) {
586
+ node.applyPendingImageSrc();
587
+ }
588
+ else if (!node.imgEl.dataset.rawSrc) {
589
+ node.imgEl.removeAttribute('src');
590
+ }
591
+ if (srcPos &&
592
+ node.imgEl.complete &&
593
+ node.imgEl.dataset.rawSrc === rawImgSrc) {
594
+ applySubTextureScaling(node, node.imgEl, srcPos);
595
+ }
596
+ if (!srcPos &&
597
+ node.imgEl.complete &&
598
+ (!supportsObjectFit || !supportsObjectPosition) &&
599
+ node.imgEl.dataset.rawSrc === rawImgSrc) {
600
+ const resizeMode = node.props.textureOptions?.resizeMode;
601
+ const clipX = resizeMode?.clipX ?? 0.5;
602
+ const clipY = resizeMode?.clipY ?? 0.5;
603
+ computeLegacyObjectFit(node, node.imgEl, resizeMode, clipX, clipY, srcPos, supportsObjectFit, supportsObjectPosition);
604
+ }
605
+ }
606
+ else {
607
+ node.lazyImagePendingSrc = null;
608
+ node.lazyImageSubTextureProps = null;
609
+ if (node.imgEl) {
610
+ node.imgEl.remove();
611
+ node.imgEl = undefined;
612
+ }
613
+ }
396
614
  }
397
615
  else {
398
- bgStyle += 'position: absolute; inset: 0; z-index: -1;';
399
- node.divBg.setAttribute('style', bgStyle);
616
+ node.lazyImagePendingSrc = null;
617
+ node.lazyImageSubTextureProps = null;
618
+ if (node.imgEl) {
619
+ node.imgEl.remove();
620
+ node.imgEl = undefined;
621
+ }
622
+ if (node.divBg) {
623
+ node.divBg.remove();
624
+ node.divBg = undefined;
625
+ }
626
+ style += bgStyle;
627
+ }
628
+ const needsSeparateBorderLayer = needsBackgroundLayer && maskStyle !== '';
629
+ if (needsSeparateBorderLayer) {
630
+ if (node.divBorder == null) {
631
+ node.divBorder = document.createElement('div');
632
+ node.div.appendChild(node.divBorder);
633
+ }
634
+ }
635
+ else if (node.divBorder) {
636
+ node.divBorder.remove();
637
+ node.divBorder = undefined;
400
638
  }
401
639
  if (node.divBorder == null) {
402
640
  style += borderStyle;
403
641
  }
404
642
  else {
405
- borderStyle += 'position: absolute; inset: 0; z-index: -1;';
406
- node.divBorder.setAttribute('style', borderStyle);
643
+ let borderLayerStyle = 'position: absolute; top:0; left:0; right:0; bottom:0; z-index: -1; pointer-events: none;';
644
+ borderLayerStyle += borderStyle;
645
+ node.divBorder.setAttribute('style', borderLayerStyle + radiusStyle);
646
+ }
647
+ }
648
+ node.div.setAttribute('style', compactString(style));
649
+ if (node instanceof DOMNode && node !== node.stage.root) {
650
+ const hasTextureSrc = nodeHasTextureSource(node);
651
+ if (hasTextureSrc && node.boundsDirty) {
652
+ const next = computeRenderStateForNode(node);
653
+ if (next != null) {
654
+ node.updateRenderState(next);
655
+ }
656
+ node.boundsDirty = false;
657
+ }
658
+ else if (!hasTextureSrc) {
659
+ node.boundsDirty = false;
407
660
  }
408
661
  }
409
- node.div.setAttribute('style', style);
410
662
  }
411
- const fontFamiliesToLoad = new Set();
412
663
  const textNodesToMeasure = new Set();
413
664
  function getElSize(node) {
414
- let rect = node.div.getBoundingClientRect();
415
- let dpr = Config.rendererOptions?.deviceLogicalPixelRatio ?? 1;
416
- rect.height /= dpr;
417
- rect.width /= dpr;
665
+ const rawRect = node.div.getBoundingClientRect();
666
+ const dpr = Config.rendererOptions?.deviceLogicalPixelRatio ?? 1;
667
+ let width = rawRect.width / dpr;
668
+ let height = rawRect.height / dpr;
418
669
  for (;;) {
419
670
  if (node.props.scale != null && node.props.scale !== 1) {
420
- rect.height /= node.props.scale;
421
- rect.width /= node.props.scale;
671
+ width /= node.props.scale;
672
+ height /= node.props.scale;
422
673
  }
423
674
  else {
424
- rect.height /= node.props.scaleY;
425
- rect.width /= node.props.scaleX;
675
+ width /= node.props.scaleX;
676
+ height /= node.props.scaleY;
426
677
  }
427
678
  if (node.parent instanceof DOMNode) {
428
679
  node = node.parent;
@@ -431,7 +682,7 @@ function getElSize(node) {
431
682
  break;
432
683
  }
433
684
  }
434
- return rect;
685
+ return { width, height };
435
686
  }
436
687
  /*
437
688
  Text nodes with contain 'width' or 'none'
@@ -446,7 +697,6 @@ function updateDOMTextSize(node) {
446
697
  if (node.props.h !== size.height) {
447
698
  node.props.h = size.height;
448
699
  updateNodeStyles(node);
449
- node.emit('loaded');
450
700
  }
451
701
  break;
452
702
  case 'none':
@@ -455,10 +705,20 @@ function updateDOMTextSize(node) {
455
705
  node.props.w = size.width;
456
706
  node.props.h = size.height;
457
707
  updateNodeStyles(node);
458
- node.emit('loaded');
459
708
  }
460
709
  break;
461
710
  }
711
+ if (!node.loaded) {
712
+ const payload = {
713
+ type: 'text',
714
+ dimensions: {
715
+ w: node.props.w,
716
+ h: node.props.h,
717
+ },
718
+ };
719
+ node.emit('loaded', payload);
720
+ node.loaded = true;
721
+ }
462
722
  }
463
723
  function updateDOMTextMeasurements() {
464
724
  textNodesToMeasure.forEach(updateDOMTextSize);
@@ -468,33 +728,36 @@ function scheduleUpdateDOMTextMeasurement(node) {
468
728
  /*
469
729
  Make sure the font is loaded before measuring
470
730
  */
471
- if (node.fontFamily && !fontFamiliesToLoad.has(node.fontFamily)) {
472
- fontFamiliesToLoad.add(node.fontFamily);
473
- document.fonts.load(`16px ${node.fontFamily}`);
474
- }
475
731
  if (textNodesToMeasure.size === 0) {
732
+ const fonts = document.fonts;
476
733
  if (document.fonts.status === 'loaded') {
477
734
  setTimeout(updateDOMTextMeasurements);
478
735
  }
479
736
  else {
480
- document.fonts.ready.then(updateDOMTextMeasurements);
737
+ if (fonts && fonts.ready && typeof fonts.ready.then === 'function') {
738
+ fonts.ready.then(updateDOMTextMeasurements);
739
+ }
740
+ else {
741
+ setTimeout(updateDOMTextMeasurements, 500);
742
+ }
481
743
  }
482
744
  }
483
745
  textNodesToMeasure.add(node);
484
746
  }
485
747
  function updateNodeData(node) {
486
- for (let key in node.data) {
487
- let keyValue = node.data[key];
748
+ const data = node.data;
749
+ for (let key in data) {
750
+ let keyValue = data[key];
488
751
  if (keyValue === undefined) {
489
752
  node.div.removeAttribute('data-' + key);
490
753
  }
491
754
  else {
492
- node.div.setAttribute('data-' + key, String(keyValue));
755
+ node.div.dataset[key] = String(keyValue);
493
756
  }
494
757
  }
495
758
  }
496
759
  function resolveNodeDefaults(props) {
497
- const color = props.color ?? 0xffffffff;
760
+ const color = props.color ?? 0x00000000;
498
761
  return {
499
762
  x: props.x ?? 0,
500
763
  y: props.y ?? 0,
@@ -568,14 +831,26 @@ const defaultShader = {
568
831
  props: undefined,
569
832
  };
570
833
  let lastNodeId = 0;
571
- class DOMNode extends EventEmitter {
834
+ const CoreNodeRenderStateMap = new Map([
835
+ [0, 'init'],
836
+ [2, 'outOfBounds'],
837
+ [4, 'inBounds'],
838
+ [8, 'inViewport'],
839
+ ]);
840
+ export class DOMNode extends EventEmitter {
572
841
  stage;
573
842
  props;
574
843
  div = document.createElement('div');
575
844
  divBg;
576
845
  divBorder;
846
+ imgEl;
847
+ lazyImagePendingSrc = null;
848
+ lazyImageSubTextureProps = null;
849
+ boundsDirty = true;
850
+ children = new Set();
577
851
  id = ++lastNodeId;
578
852
  renderState = 0 /* Init */;
853
+ preventCleanup = true;
579
854
  constructor(stage, props) {
580
855
  super();
581
856
  this.stage = stage;
@@ -584,62 +859,152 @@ class DOMNode extends EventEmitter {
584
859
  this.div._node = this;
585
860
  this.div.setAttribute('data-id', String(this.id));
586
861
  elMap.set(this, this.div);
862
+ const parent = this.props.parent;
863
+ if (parent instanceof DOMNode) {
864
+ parent.children.add(this);
865
+ }
587
866
  updateNodeParent(this);
588
867
  updateNodeStyles(this);
589
868
  updateNodeData(this);
590
869
  }
591
870
  destroy() {
592
871
  elMap.delete(this);
872
+ const parent = this.props.parent;
873
+ if (parent instanceof DOMNode) {
874
+ parent.children.delete(this);
875
+ }
593
876
  this.div.parentNode.removeChild(this.div);
594
877
  }
595
878
  get parent() {
596
879
  return this.props.parent;
597
880
  }
598
881
  set parent(value) {
882
+ if (this.props.parent === value)
883
+ return;
884
+ const prevParent = this.props.parent;
885
+ if (prevParent instanceof DOMNode) {
886
+ prevParent.children.delete(this);
887
+ prevParent.markChildrenBoundsDirty();
888
+ }
599
889
  this.props.parent = value;
890
+ if (value instanceof DOMNode) {
891
+ value.children.add(this);
892
+ value.markChildrenBoundsDirty();
893
+ }
894
+ this.boundsDirty = true;
895
+ this.markChildrenBoundsDirty();
600
896
  updateNodeParent(this);
601
897
  }
898
+ markChildrenBoundsDirty() {
899
+ for (const child of this.children) {
900
+ child.boundsDirty = true;
901
+ if (child !== child.stage.root) {
902
+ if (nodeHasTextureSource(child)) {
903
+ const nextState = computeRenderStateForNode(child);
904
+ if (nextState != null) {
905
+ child.updateRenderState(nextState);
906
+ }
907
+ }
908
+ child.boundsDirty = false;
909
+ }
910
+ child.markChildrenBoundsDirty();
911
+ }
912
+ }
602
913
  animate = animate;
914
+ updateRenderState(renderState) {
915
+ if (renderState === this.renderState)
916
+ return;
917
+ const previous = this.renderState;
918
+ this.renderState = renderState;
919
+ const event = CoreNodeRenderStateMap.get(renderState);
920
+ if (isRenderStateInBounds(renderState)) {
921
+ this.applyPendingImageSrc();
922
+ }
923
+ if (event && event !== 'init') {
924
+ this.emit(event, { previous, current: renderState });
925
+ }
926
+ if (this.imgEl) {
927
+ this.imgEl.dataset.state = event;
928
+ }
929
+ }
930
+ applyPendingImageSrc() {
931
+ if (!this.imgEl)
932
+ return;
933
+ const pendingSrc = this.lazyImagePendingSrc;
934
+ if (!pendingSrc)
935
+ return;
936
+ if (this.imgEl.dataset.rawSrc === pendingSrc)
937
+ return;
938
+ this.imgEl.style.display = '';
939
+ this.imgEl.dataset.pendingSrc = pendingSrc;
940
+ this.imgEl.src = pendingSrc;
941
+ this.imgEl.dataset.rawSrc = pendingSrc;
942
+ this.imgEl.dataset.pendingSrc = '';
943
+ }
603
944
  get x() {
604
945
  return this.props.x;
605
946
  }
606
947
  set x(v) {
948
+ if (this.props.x === v)
949
+ return;
607
950
  this.props.x = v;
951
+ this.boundsDirty = true;
952
+ this.markChildrenBoundsDirty();
608
953
  updateNodeStyles(this);
609
954
  }
610
955
  get y() {
611
956
  return this.props.y;
612
957
  }
613
958
  set y(v) {
959
+ if (this.props.y === v)
960
+ return;
614
961
  this.props.y = v;
962
+ this.boundsDirty = true;
963
+ this.markChildrenBoundsDirty();
615
964
  updateNodeStyles(this);
616
965
  }
617
966
  get w() {
618
967
  return this.props.w;
619
968
  }
620
969
  set w(v) {
970
+ if (this.props.w === v)
971
+ return;
621
972
  this.props.w = v;
973
+ this.boundsDirty = true;
974
+ this.markChildrenBoundsDirty();
622
975
  updateNodeStyles(this);
623
976
  }
624
977
  get h() {
625
978
  return this.props.h;
626
979
  }
627
980
  set h(v) {
981
+ if (this.props.h === v)
982
+ return;
628
983
  this.props.h = v;
984
+ this.boundsDirty = true;
985
+ this.markChildrenBoundsDirty();
629
986
  updateNodeStyles(this);
630
987
  }
631
988
  get width() {
632
989
  return this.props.w;
633
990
  }
634
991
  set width(v) {
992
+ if (this.props.w === v)
993
+ return;
635
994
  this.props.w = v;
995
+ this.boundsDirty = true;
996
+ this.markChildrenBoundsDirty();
636
997
  updateNodeStyles(this);
637
998
  }
638
999
  get height() {
639
1000
  return this.props.h;
640
1001
  }
641
1002
  set height(v) {
1003
+ if (this.props.h === v)
1004
+ return;
642
1005
  this.props.h = v;
1006
+ this.boundsDirty = true;
1007
+ this.markChildrenBoundsDirty();
643
1008
  updateNodeStyles(this);
644
1009
  }
645
1010
  get alpha() {
@@ -730,14 +1095,19 @@ class DOMNode extends EventEmitter {
730
1095
  return this.props.zIndex;
731
1096
  }
732
1097
  set zIndex(v) {
733
- this.props.zIndex = v;
1098
+ if (this.props.zIndex === v)
1099
+ return;
1100
+ this.props.zIndex = Math.ceil(v);
734
1101
  updateNodeStyles(this);
735
1102
  }
736
1103
  get texture() {
737
1104
  return this.props.texture;
738
1105
  }
739
1106
  set texture(v) {
1107
+ if (this.props.texture === v)
1108
+ return;
740
1109
  this.props.texture = v;
1110
+ this.boundsDirty = true;
741
1111
  updateNodeStyles(this);
742
1112
  }
743
1113
  get textureOptions() {
@@ -751,13 +1121,18 @@ class DOMNode extends EventEmitter {
751
1121
  return this.props.src;
752
1122
  }
753
1123
  set src(v) {
1124
+ if (this.props.src === v)
1125
+ return;
754
1126
  this.props.src = v;
1127
+ this.boundsDirty = true;
755
1128
  updateNodeStyles(this);
756
1129
  }
757
1130
  get scale() {
758
1131
  return this.props.scale ?? 1;
759
1132
  }
760
1133
  set scale(v) {
1134
+ if (this.props.scale === v)
1135
+ return;
761
1136
  this.props.scale = v;
762
1137
  updateNodeStyles(this);
763
1138
  }
@@ -880,25 +1255,37 @@ class DOMNode extends EventEmitter {
880
1255
  }
881
1256
  set boundsMargin(value) {
882
1257
  this.props.boundsMargin = value;
1258
+ this.boundsDirty = true;
1259
+ this.markChildrenBoundsDirty();
883
1260
  }
884
1261
  get absX() {
885
- return this.x + -this.width * this.mountX + (this.parent?.absX ?? 0);
1262
+ const parent = this.props.parent;
1263
+ return (this.x +
1264
+ -this.w * this.mountX +
1265
+ (parent instanceof DOMNode ? parent.absX : 0));
886
1266
  }
887
1267
  get absY() {
888
- return this.y + -this.height * this.mountY + (this.parent?.absY ?? 0);
1268
+ const parent = this.props.parent;
1269
+ return (this.y +
1270
+ -this.h * this.mountY +
1271
+ (parent instanceof DOMNode ? parent.absY : 0));
889
1272
  }
890
1273
  }
891
1274
  class DOMText extends DOMNode {
892
1275
  props;
1276
+ loaded = false;
893
1277
  constructor(stage, props) {
894
1278
  super(stage, props);
895
1279
  this.props = props;
896
1280
  this.div.innerText = props.text;
1281
+ scheduleUpdateDOMTextMeasurement(this);
897
1282
  }
898
1283
  get text() {
899
1284
  return this.props.text;
900
1285
  }
901
1286
  set text(v) {
1287
+ if (this.props.text === v)
1288
+ return;
902
1289
  this.props.text = v;
903
1290
  this.div.innerText = v;
904
1291
  scheduleUpdateDOMTextMeasurement(this);
@@ -907,29 +1294,51 @@ class DOMText extends DOMNode {
907
1294
  return this.props.fontFamily;
908
1295
  }
909
1296
  set fontFamily(v) {
1297
+ if (this.props.fontFamily === v)
1298
+ return;
910
1299
  this.props.fontFamily = v;
911
1300
  updateNodeStyles(this);
1301
+ scheduleUpdateDOMTextMeasurement(this);
912
1302
  }
913
1303
  get fontSize() {
914
1304
  return this.props.fontSize;
915
1305
  }
916
1306
  set fontSize(v) {
1307
+ if (this.props.fontSize === v)
1308
+ return;
917
1309
  this.props.fontSize = v;
918
1310
  updateNodeStyles(this);
1311
+ scheduleUpdateDOMTextMeasurement(this);
919
1312
  }
920
1313
  get fontStyle() {
921
1314
  return this.props.fontStyle;
922
1315
  }
923
1316
  set fontStyle(v) {
1317
+ if (this.props.fontStyle === v)
1318
+ return;
924
1319
  this.props.fontStyle = v;
925
1320
  updateNodeStyles(this);
1321
+ scheduleUpdateDOMTextMeasurement(this);
926
1322
  }
927
1323
  get fontWeight() {
928
1324
  return this.props.fontWeight;
929
1325
  }
930
1326
  set fontWeight(v) {
1327
+ if (this.props.fontWeight === v)
1328
+ return;
931
1329
  this.props.fontWeight = v;
932
1330
  updateNodeStyles(this);
1331
+ scheduleUpdateDOMTextMeasurement(this);
1332
+ }
1333
+ get fontStretch() {
1334
+ return this.props.fontStretch;
1335
+ }
1336
+ set fontStretch(v) {
1337
+ if (this.props.fontStretch === v)
1338
+ return;
1339
+ this.props.fontStretch = v;
1340
+ updateNodeStyles(this);
1341
+ scheduleUpdateDOMTextMeasurement(this);
933
1342
  }
934
1343
  get forceLoad() {
935
1344
  return this.props.forceLoad;
@@ -941,34 +1350,48 @@ class DOMText extends DOMNode {
941
1350
  return this.props.lineHeight;
942
1351
  }
943
1352
  set lineHeight(v) {
1353
+ if (this.props.lineHeight === v)
1354
+ return;
944
1355
  this.props.lineHeight = v;
945
1356
  updateNodeStyles(this);
1357
+ scheduleUpdateDOMTextMeasurement(this);
946
1358
  }
947
1359
  get maxWidth() {
948
1360
  return this.props.maxWidth;
949
1361
  }
950
1362
  set maxWidth(v) {
1363
+ if (this.props.maxWidth === v)
1364
+ return;
951
1365
  this.props.maxWidth = v;
952
1366
  updateNodeStyles(this);
1367
+ scheduleUpdateDOMTextMeasurement(this);
953
1368
  }
954
1369
  get maxHeight() {
955
1370
  return this.props.maxHeight;
956
1371
  }
957
1372
  set maxHeight(v) {
1373
+ if (this.props.maxHeight === v)
1374
+ return;
958
1375
  this.props.maxHeight = v;
959
1376
  updateNodeStyles(this);
1377
+ scheduleUpdateDOMTextMeasurement(this);
960
1378
  }
961
1379
  get letterSpacing() {
962
1380
  return this.props.letterSpacing;
963
1381
  }
964
1382
  set letterSpacing(v) {
1383
+ if (this.props.letterSpacing === v)
1384
+ return;
965
1385
  this.props.letterSpacing = v;
966
1386
  updateNodeStyles(this);
1387
+ scheduleUpdateDOMTextMeasurement(this);
967
1388
  }
968
1389
  get textAlign() {
969
1390
  return this.props.textAlign;
970
1391
  }
971
1392
  set textAlign(v) {
1393
+ if (this.props.textAlign === v)
1394
+ return;
972
1395
  this.props.textAlign = v;
973
1396
  updateNodeStyles(this);
974
1397
  }
@@ -976,6 +1399,8 @@ class DOMText extends DOMNode {
976
1399
  return this.props.overflowSuffix;
977
1400
  }
978
1401
  set overflowSuffix(v) {
1402
+ if (this.props.overflowSuffix === v)
1403
+ return;
979
1404
  this.props.overflowSuffix = v;
980
1405
  updateNodeStyles(this);
981
1406
  }
@@ -983,15 +1408,21 @@ class DOMText extends DOMNode {
983
1408
  return this.props.maxLines;
984
1409
  }
985
1410
  set maxLines(v) {
1411
+ if (this.props.maxLines === v)
1412
+ return;
986
1413
  this.props.maxLines = v;
987
1414
  updateNodeStyles(this);
1415
+ scheduleUpdateDOMTextMeasurement(this);
988
1416
  }
989
1417
  get contain() {
990
1418
  return this.props.contain;
991
1419
  }
992
1420
  set contain(v) {
1421
+ if (this.props.contain === v)
1422
+ return;
993
1423
  this.props.contain = v;
994
1424
  updateNodeStyles(this);
1425
+ scheduleUpdateDOMTextMeasurement(this);
995
1426
  }
996
1427
  get verticalAlign() {
997
1428
  return this.props.verticalAlign;
@@ -1044,6 +1475,7 @@ export class DOMRendererMain {
1044
1475
  root;
1045
1476
  canvas;
1046
1477
  stage;
1478
+ eventListeners = new Map();
1047
1479
  constructor(settings, rawTarget) {
1048
1480
  this.settings = settings;
1049
1481
  let target;
@@ -1070,22 +1502,27 @@ export class DOMRendererMain {
1070
1502
  root: null,
1071
1503
  renderer: {
1072
1504
  mode: 'canvas',
1505
+ boundsMargin: settings.boundsMargin,
1073
1506
  },
1074
- loadFont: async () => { },
1075
1507
  shManager: {
1076
1508
  registerShaderType() { },
1077
1509
  },
1078
1510
  animationManager: {
1079
- registerAnimation() { },
1080
- unregisterAnimation() { },
1511
+ registerAnimation(anim) {
1512
+ console.log('registerAnimation', anim);
1513
+ },
1514
+ unregisterAnimation(anim) {
1515
+ console.log('unregisterAnimation', anim);
1516
+ },
1081
1517
  },
1518
+ loadFont: async () => { },
1082
1519
  cleanup() { },
1083
1520
  };
1084
1521
  this.root = new DOMNode(this.stage, resolveNodeDefaults({
1085
1522
  w: settings.appWidth ?? 1920,
1086
1523
  h: settings.appHeight ?? 1080,
1087
1524
  shader: defaultShader,
1088
- zIndex: 65534,
1525
+ zIndex: 1,
1089
1526
  }));
1090
1527
  this.stage.root = this.root;
1091
1528
  target.appendChild(this.root.div);
@@ -1116,14 +1553,76 @@ export class DOMRendererMain {
1116
1553
  new ResizeObserver(updateRootPosition.bind(this)).observe(this.canvas);
1117
1554
  window.addEventListener('resize', updateRootPosition.bind(this));
1118
1555
  }
1556
+ removeAllListeners() {
1557
+ if (this.eventListeners.size === 0)
1558
+ return;
1559
+ this.eventListeners.forEach((listeners) => listeners.clear());
1560
+ this.eventListeners.clear();
1561
+ }
1562
+ once(event, listener) {
1563
+ const wrappedListener = (target, data) => {
1564
+ this.off(event, wrappedListener);
1565
+ listener(target, data);
1566
+ };
1567
+ this.on(event, wrappedListener);
1568
+ }
1569
+ on(name, callback) {
1570
+ let listeners = this.eventListeners.get(name);
1571
+ if (!listeners) {
1572
+ listeners = new Set();
1573
+ this.eventListeners.set(name, listeners);
1574
+ }
1575
+ listeners.add(callback);
1576
+ }
1577
+ off(event, listener) {
1578
+ const listeners = this.eventListeners.get(event);
1579
+ if (listeners) {
1580
+ listeners.delete(listener);
1581
+ if (listeners.size === 0) {
1582
+ this.eventListeners.delete(event);
1583
+ }
1584
+ }
1585
+ }
1586
+ emit(event, targetOrData, maybeData) {
1587
+ const listeners = this.eventListeners.get(event);
1588
+ if (!listeners || listeners.size === 0) {
1589
+ return;
1590
+ }
1591
+ const hasExplicitTarget = arguments.length === 3;
1592
+ const target = hasExplicitTarget ? targetOrData : this.root;
1593
+ const data = hasExplicitTarget ? maybeData : targetOrData;
1594
+ for (const listener of Array.from(listeners)) {
1595
+ try {
1596
+ listener(target, data);
1597
+ }
1598
+ catch (error) {
1599
+ console.error(`Error in listener for event "${event}"`, error);
1600
+ }
1601
+ }
1602
+ }
1119
1603
  createNode(props) {
1120
1604
  return new DOMNode(this.stage, resolveNodeDefaults(props));
1121
1605
  }
1122
1606
  createTextNode(props) {
1123
1607
  return new DOMText(this.stage, resolveTextNodeDefaults(props));
1124
1608
  }
1125
- createShader(shaderType, props) {
1126
- return { shaderType, props, program: {} };
1609
+ /** TODO: restore this */
1610
+ // createShader<ShType extends keyof ShaderMap>(
1611
+ // shType: ShType,
1612
+ // props?: OptionalShaderProps<ShType>,
1613
+ // ): InstanceType<lng.ShaderMap[ShType]> {
1614
+ // return { shaderType: shType, props, program: {} } as InstanceType<
1615
+ // lng.ShaderMap[ShType]
1616
+ // >;
1617
+ // }
1618
+ createShader(...args) {
1619
+ const [shaderType, props] = args;
1620
+ return {
1621
+ // @ts-ignore
1622
+ shaderType,
1623
+ props,
1624
+ program: {},
1625
+ };
1127
1626
  }
1128
1627
  createTexture(textureType, props) {
1129
1628
  let type = lng.TextureType.generic;
@@ -1146,8 +1645,20 @@ export class DOMRendererMain {
1146
1645
  }
1147
1646
  return { type, props };
1148
1647
  }
1149
- on(name, callback) {
1150
- console.log('on', name, callback);
1648
+ }
1649
+ export function loadFontToDom(font) {
1650
+ // fontFamily: string;
1651
+ // metrics?: FontMetrics;
1652
+ // fontUrl?: string;
1653
+ // atlasUrl?: string;
1654
+ // atlasDataUrl?: string;
1655
+ const fontFace = new FontFace(font.fontFamily, `url(${font.fontUrl})`);
1656
+ if (typeof document !== 'undefined' && 'fonts' in document) {
1657
+ const fontSet = document.fonts;
1658
+ fontSet.add?.(fontFace);
1151
1659
  }
1152
1660
  }
1661
+ export function isDomRenderer(r) {
1662
+ return r instanceof DOMRendererMain;
1663
+ }
1153
1664
  //# sourceMappingURL=domRenderer.js.map